@gallop.software/studio 0.1.92 → 0.1.94
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-7VQHCHFQ.mjs → StudioUI-GKE5ZWKW.mjs} +1155 -585
- package/dist/StudioUI-GKE5ZWKW.mjs.map +1 -0
- package/dist/{StudioUI-VRSG32E3.js → StudioUI-QBIGDYYL.js} +811 -241
- package/dist/StudioUI-QBIGDYYL.js.map +1 -0
- package/dist/{chunk-DTVEVFQ2.mjs → chunk-IHXG2EE4.mjs} +1 -1
- package/dist/chunk-IHXG2EE4.mjs.map +1 -0
- package/dist/{chunk-L36EH3PM.js → chunk-MCJNUXQ6.js} +1 -1
- package/dist/chunk-MCJNUXQ6.js.map +1 -0
- package/dist/handlers/index.d.mts +1 -15
- package/dist/handlers/index.d.ts +1 -15
- package/dist/handlers/index.js +222 -42
- package/dist/handlers/index.js.map +1 -1
- package/dist/handlers/index.mjs +209 -29
- package/dist/handlers/index.mjs.map +1 -1
- package/dist/index.d.mts +62 -2
- package/dist/index.d.ts +62 -2
- package/dist/index.js +3 -3
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
- package/dist/StudioUI-7VQHCHFQ.mjs.map +0 -1
- package/dist/StudioUI-VRSG32E3.js.map +0 -1
- package/dist/chunk-DTVEVFQ2.mjs.map +0 -1
- package/dist/chunk-L36EH3PM.js.map +0 -1
- package/dist/types-C4hCz2w8.d.mts +0 -62
- package/dist/types-C4hCz2w8.d.ts +0 -62
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["/**\n * Meta entry - works for images and non-images\n * Images have w, h, b (after processing)\n * c is the index into _cdns array (omit if not on CDN)\n */\nexport interface MetaEntry {\n w?: number // original width (images only)\n h?: number // original height (images only)\n b?: string // blurhash (images only, after processing)\n p?: 1 // processed (has thumbnails and blurhash)\n c?: number // CDN index - index into _cdns array (omit if not on CDN)\n}\n\n/**\n * Full meta schema including special keys\n * _cdns: Array of CDN base URLs\n * Other keys: file paths from public folder\n */\nexport interface FullMeta {\n _cdns?: string[] // Array of CDN base URLs\n [key: string]: MetaEntry | string[] | undefined\n}\n\n/**\n * Meta schema - keyed by path from public folder (legacy type)\n * Example: { \"/portfolio/photo.jpg\": { w: 2400, h: 1600, b: \"...\" } }\n */\nexport type LeanMeta = Record<string, MetaEntry>\n\n// Legacy alias for compatibility\nexport type LeanImageEntry = MetaEntry\n\n/**\n * File/folder item for browser\n */\nexport interface FileItem {\n name: string\n path: string\n type: 'file' | 'folder'\n size?: number\n dimensions?: { width: number; height: number }\n isProcessed?: boolean\n cdnPushed?: boolean\n // Folder-specific properties\n fileCount?: number\n totalSize?: number\n // For showing thumbnails - path to -sm version if exists\n thumbnail?: string\n // Whether a processed thumbnail exists\n hasThumbnail?: boolean\n}\n\n/**\n * Studio configuration\n */\nexport interface StudioConfig {\n r2AccountId?: string\n r2AccessKeyId?: string\n r2SecretAccessKey?: string\n r2BucketName?: string\n r2PublicUrl?: string\n thumbnailSizes?: {\n small: number\n medium: number\n large: number\n }\n}\n\n/**\n * Get thumbnail path from original image path\n */\nexport function getThumbnailPath(originalPath: string, size: 'sm' | 'md' | 'lg' | 'full'): string {\n if (size === 'full') {\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}${outputExt}`\n }\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}-${size}${outputExt}`\n}\n\n/**\n * Get all thumbnail paths for an image\n */\nexport function getAllThumbnailPaths(originalPath: string): string[] {\n return [\n getThumbnailPath(originalPath, 'full'),\n getThumbnailPath(originalPath, 'lg'),\n getThumbnailPath(originalPath, 'md'),\n getThumbnailPath(originalPath, 'sm'),\n ]\n}\n"],"mappings":";AAuEO,SAAS,iBAAiB,cAAsB,MAA2C;AAChG,MAAI,SAAS,QAAQ;AACnB,UAAMA,OAAM,aAAa,MAAM,QAAQ,IAAI,CAAC,KAAK;AACjD,UAAMC,QAAO,aAAa,QAAQ,UAAU,EAAE;AAC9C,UAAMC,aAAYF,KAAI,YAAY,MAAM,SAAS,SAAS;AAC1D,WAAO,UAAUC,KAAI,GAAGC,UAAS;AAAA,EACnC;AACA,QAAM,MAAM,aAAa,MAAM,QAAQ,IAAI,CAAC,KAAK;AACjD,QAAM,OAAO,aAAa,QAAQ,UAAU,EAAE;AAC9C,QAAM,YAAY,IAAI,YAAY,MAAM,SAAS,SAAS;AAC1D,SAAO,UAAU,IAAI,IAAI,IAAI,GAAG,SAAS;AAC3C;AAKO,SAAS,qBAAqB,cAAgC;AACnE,SAAO;AAAA,IACL,iBAAiB,cAAc,MAAM;AAAA,IACrC,iBAAiB,cAAc,IAAI;AAAA,IACnC,iBAAiB,cAAc,IAAI;AAAA,IACnC,iBAAiB,cAAc,IAAI;AAAA,EACrC;AACF;","names":["ext","base","outputExt"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/chrisb/Sites/studio/dist/chunk-MCJNUXQ6.js","../src/types.ts"],"names":["ext","base","outputExt"],"mappings":"AAAA;ACuEO,SAAS,gBAAA,CAAiB,YAAA,EAAsB,IAAA,EAA2C;AAChG,EAAA,GAAA,CAAI,KAAA,IAAS,MAAA,EAAQ;AACnB,IAAA,MAAMA,KAAAA,kBAAM,YAAA,mBAAa,KAAA,mBAAM,QAAQ,CAAA,4BAAA,CAAI,CAAC,IAAA,GAAK,MAAA;AACjD,IAAA,MAAMC,MAAAA,EAAO,YAAA,CAAa,OAAA,CAAQ,QAAA,EAAU,EAAE,CAAA;AAC9C,IAAA,MAAMC,WAAAA,EAAYF,IAAAA,CAAI,WAAA,CAAY,EAAA,IAAM,OAAA,EAAS,OAAA,EAAS,MAAA;AAC1D,IAAA,OAAO,CAAA,OAAA,EAAUC,KAAI,CAAA,EAAA;AACvB,EAAA;AACyB,EAAA;AACZ,EAAA;AACS,EAAA;AACG,EAAA;AAC3B;AAKgB;AACP,EAAA;AACY,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACnB,EAAA;AACF;ADzE2B;AACA;AACA;AACA;AACA","file":"/Users/chrisb/Sites/studio/dist/chunk-MCJNUXQ6.js","sourcesContent":[null,"/**\n * Meta entry - works for images and non-images\n * Images have w, h, b (after processing)\n * c is the index into _cdns array (omit if not on CDN)\n */\nexport interface MetaEntry {\n w?: number // original width (images only)\n h?: number // original height (images only)\n b?: string // blurhash (images only, after processing)\n p?: 1 // processed (has thumbnails and blurhash)\n c?: number // CDN index - index into _cdns array (omit if not on CDN)\n}\n\n/**\n * Full meta schema including special keys\n * _cdns: Array of CDN base URLs\n * Other keys: file paths from public folder\n */\nexport interface FullMeta {\n _cdns?: string[] // Array of CDN base URLs\n [key: string]: MetaEntry | string[] | undefined\n}\n\n/**\n * Meta schema - keyed by path from public folder (legacy type)\n * Example: { \"/portfolio/photo.jpg\": { w: 2400, h: 1600, b: \"...\" } }\n */\nexport type LeanMeta = Record<string, MetaEntry>\n\n// Legacy alias for compatibility\nexport type LeanImageEntry = MetaEntry\n\n/**\n * File/folder item for browser\n */\nexport interface FileItem {\n name: string\n path: string\n type: 'file' | 'folder'\n size?: number\n dimensions?: { width: number; height: number }\n isProcessed?: boolean\n cdnPushed?: boolean\n // Folder-specific properties\n fileCount?: number\n totalSize?: number\n // For showing thumbnails - path to -sm version if exists\n thumbnail?: string\n // Whether a processed thumbnail exists\n hasThumbnail?: boolean\n}\n\n/**\n * Studio configuration\n */\nexport interface StudioConfig {\n r2AccountId?: string\n r2AccessKeyId?: string\n r2SecretAccessKey?: string\n r2BucketName?: string\n r2PublicUrl?: string\n thumbnailSizes?: {\n small: number\n medium: number\n large: number\n }\n}\n\n/**\n * Get thumbnail path from original image path\n */\nexport function getThumbnailPath(originalPath: string, size: 'sm' | 'md' | 'lg' | 'full'): string {\n if (size === 'full') {\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}${outputExt}`\n }\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}-${size}${outputExt}`\n}\n\n/**\n * Get all thumbnail paths for an image\n */\nexport function getAllThumbnailPaths(originalPath: string): string[] {\n return [\n getThumbnailPath(originalPath, 'full'),\n getThumbnailPath(originalPath, 'lg'),\n getThumbnailPath(originalPath, 'md'),\n getThumbnailPath(originalPath, 'sm'),\n ]\n}\n"]}
|
|
@@ -1,23 +1,9 @@
|
|
|
1
|
-
import { F as FileItem } from '../types-C4hCz2w8.mjs';
|
|
2
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Unified GET handler for all Studio API routes
|
|
6
5
|
*/
|
|
7
|
-
declare function GET(request: NextRequest): Promise<
|
|
8
|
-
error: string;
|
|
9
|
-
}> | NextResponse<{
|
|
10
|
-
folders: {
|
|
11
|
-
path: string;
|
|
12
|
-
name: string;
|
|
13
|
-
depth: number;
|
|
14
|
-
}[];
|
|
15
|
-
}> | NextResponse<{
|
|
16
|
-
items: FileItem[];
|
|
17
|
-
}> | NextResponse<{
|
|
18
|
-
count: number;
|
|
19
|
-
images: string[];
|
|
20
|
-
}>>;
|
|
6
|
+
declare function GET(request: NextRequest): Promise<Response>;
|
|
21
7
|
/**
|
|
22
8
|
* Unified POST handler for all Studio API routes
|
|
23
9
|
*/
|
package/dist/handlers/index.d.ts
CHANGED
|
@@ -1,23 +1,9 @@
|
|
|
1
|
-
import { F as FileItem } from '../types-C4hCz2w8.js';
|
|
2
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Unified GET handler for all Studio API routes
|
|
6
5
|
*/
|
|
7
|
-
declare function GET(request: NextRequest): Promise<
|
|
8
|
-
error: string;
|
|
9
|
-
}> | NextResponse<{
|
|
10
|
-
folders: {
|
|
11
|
-
path: string;
|
|
12
|
-
name: string;
|
|
13
|
-
depth: number;
|
|
14
|
-
}[];
|
|
15
|
-
}> | NextResponse<{
|
|
16
|
-
items: FileItem[];
|
|
17
|
-
}> | NextResponse<{
|
|
18
|
-
count: number;
|
|
19
|
-
images: string[];
|
|
20
|
-
}>>;
|
|
6
|
+
declare function GET(request: NextRequest): Promise<Response>;
|
|
21
7
|
/**
|
|
22
8
|
* Unified POST handler for all Studio API routes
|
|
23
9
|
*/
|
package/dist/handlers/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var _chunkMCJNUXQ6js = require('../chunk-MCJNUXQ6.js');
|
|
5
5
|
|
|
6
6
|
// src/handlers/index.ts
|
|
7
7
|
var _server = require('next/server');
|
|
@@ -29,6 +29,35 @@ async function saveMeta(meta) {
|
|
|
29
29
|
const metaPath = _path2.default.join(dataDir, "_meta.json");
|
|
30
30
|
await _fs.promises.writeFile(metaPath, JSON.stringify(meta, null, 2));
|
|
31
31
|
}
|
|
32
|
+
function getCdnUrls(meta) {
|
|
33
|
+
return meta._cdns || [];
|
|
34
|
+
}
|
|
35
|
+
function getOrAddCdnIndex(meta, cdnUrl) {
|
|
36
|
+
if (!meta._cdns) {
|
|
37
|
+
meta._cdns = [];
|
|
38
|
+
}
|
|
39
|
+
const normalizedUrl = cdnUrl.replace(/\/$/, "");
|
|
40
|
+
const existingIndex = meta._cdns.indexOf(normalizedUrl);
|
|
41
|
+
if (existingIndex >= 0) {
|
|
42
|
+
return existingIndex;
|
|
43
|
+
}
|
|
44
|
+
meta._cdns.push(normalizedUrl);
|
|
45
|
+
return meta._cdns.length - 1;
|
|
46
|
+
}
|
|
47
|
+
function getMetaEntry(meta, key) {
|
|
48
|
+
if (key.startsWith("_")) return void 0;
|
|
49
|
+
const value = meta[key];
|
|
50
|
+
if (Array.isArray(value)) return void 0;
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
function setMetaEntry(meta, key, entry) {
|
|
54
|
+
meta[key] = entry;
|
|
55
|
+
}
|
|
56
|
+
function getFileEntries(meta) {
|
|
57
|
+
return Object.entries(meta).filter(
|
|
58
|
+
([key, value]) => !key.startsWith("_") && !Array.isArray(value)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
32
61
|
|
|
33
62
|
// src/handlers/utils/files.ts
|
|
34
63
|
|
|
@@ -156,7 +185,7 @@ async function uploadToCdn(imageKey) {
|
|
|
156
185
|
const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
|
|
157
186
|
if (!bucketName) throw new Error("R2 bucket not configured");
|
|
158
187
|
const r2 = getR2Client();
|
|
159
|
-
for (const thumbPath of
|
|
188
|
+
for (const thumbPath of _chunkMCJNUXQ6js.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
160
189
|
const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
161
190
|
try {
|
|
162
191
|
const fileBuffer = await _fs.promises.readFile(localPath);
|
|
@@ -173,7 +202,7 @@ async function uploadToCdn(imageKey) {
|
|
|
173
202
|
}
|
|
174
203
|
}
|
|
175
204
|
async function deleteLocalThumbnails(imageKey) {
|
|
176
|
-
for (const thumbPath of
|
|
205
|
+
for (const thumbPath of _chunkMCJNUXQ6js.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
177
206
|
const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
178
207
|
try {
|
|
179
208
|
await _fs.promises.unlink(localPath);
|
|
@@ -188,16 +217,17 @@ async function handleList(request) {
|
|
|
188
217
|
const requestedPath = searchParams.get("path") || "public";
|
|
189
218
|
try {
|
|
190
219
|
const meta = await loadMeta();
|
|
191
|
-
const
|
|
192
|
-
|
|
220
|
+
const fileEntries = getFileEntries(meta);
|
|
221
|
+
const cdnUrls = getCdnUrls(meta);
|
|
222
|
+
if (fileEntries.length === 0) {
|
|
193
223
|
return _server.NextResponse.json({ items: [], isEmpty: true });
|
|
194
224
|
}
|
|
195
225
|
const relativePath = requestedPath.replace(/^public\/?/, "");
|
|
196
226
|
const pathPrefix = relativePath ? `/${relativePath}/` : "/";
|
|
197
227
|
const items = [];
|
|
198
228
|
const seenFolders = /* @__PURE__ */ new Set();
|
|
199
|
-
|
|
200
|
-
|
|
229
|
+
const metaKeys = fileEntries.map(([key]) => key);
|
|
230
|
+
for (const [key, entry] of fileEntries) {
|
|
201
231
|
if (!key.startsWith(pathPrefix) && pathPrefix !== "/") continue;
|
|
202
232
|
if (pathPrefix === "/" && !key.startsWith("/")) continue;
|
|
203
233
|
const remaining = pathPrefix === "/" ? key.slice(1) : key.slice(pathPrefix.length);
|
|
@@ -222,14 +252,14 @@ async function handleList(request) {
|
|
|
222
252
|
} else {
|
|
223
253
|
const fileName = remaining;
|
|
224
254
|
const isImage = isImageFile(fileName);
|
|
225
|
-
const isPushedToCloud = entry.c
|
|
255
|
+
const isPushedToCloud = entry.c !== void 0;
|
|
226
256
|
let thumbnail;
|
|
227
257
|
let hasThumbnail = false;
|
|
228
258
|
let fileSize;
|
|
229
259
|
if (isImage && (entry.w || entry.b)) {
|
|
230
|
-
const thumbPath =
|
|
231
|
-
if (isPushedToCloud) {
|
|
232
|
-
const cdnUrl =
|
|
260
|
+
const thumbPath = _chunkMCJNUXQ6js.getThumbnailPath.call(void 0, key, "sm");
|
|
261
|
+
if (isPushedToCloud && entry.c !== void 0) {
|
|
262
|
+
const cdnUrl = cdnUrls[entry.c];
|
|
233
263
|
if (cdnUrl) {
|
|
234
264
|
thumbnail = `${cdnUrl}${thumbPath}`;
|
|
235
265
|
hasThumbnail = true;
|
|
@@ -284,19 +314,21 @@ async function handleSearch(request) {
|
|
|
284
314
|
}
|
|
285
315
|
try {
|
|
286
316
|
const meta = await loadMeta();
|
|
317
|
+
const fileEntries = getFileEntries(meta);
|
|
318
|
+
const cdnUrls = getCdnUrls(meta);
|
|
287
319
|
const items = [];
|
|
288
|
-
for (const [key, entry] of
|
|
320
|
+
for (const [key, entry] of fileEntries) {
|
|
289
321
|
if (!key.toLowerCase().includes(query)) continue;
|
|
290
322
|
const fileName = _path2.default.basename(key);
|
|
291
323
|
const relativePath = key.slice(1);
|
|
292
324
|
const isImage = isImageFile(fileName);
|
|
293
|
-
const isPushedToCloud = entry.c
|
|
325
|
+
const isPushedToCloud = entry.c !== void 0;
|
|
294
326
|
let thumbnail;
|
|
295
327
|
let hasThumbnail = false;
|
|
296
328
|
if (isImage && (entry.w || entry.b)) {
|
|
297
|
-
const thumbPath =
|
|
298
|
-
if (isPushedToCloud) {
|
|
299
|
-
const cdnUrl =
|
|
329
|
+
const thumbPath = _chunkMCJNUXQ6js.getThumbnailPath.call(void 0, key, "sm");
|
|
330
|
+
if (isPushedToCloud && entry.c !== void 0) {
|
|
331
|
+
const cdnUrl = cdnUrls[entry.c];
|
|
300
332
|
if (cdnUrl) {
|
|
301
333
|
thumbnail = `${cdnUrl}${thumbPath}`;
|
|
302
334
|
hasThumbnail = true;
|
|
@@ -336,8 +368,9 @@ async function handleSearch(request) {
|
|
|
336
368
|
async function handleListFolders() {
|
|
337
369
|
try {
|
|
338
370
|
const meta = await loadMeta();
|
|
371
|
+
const fileEntries = getFileEntries(meta);
|
|
339
372
|
const folderSet = /* @__PURE__ */ new Set();
|
|
340
|
-
for (const key of
|
|
373
|
+
for (const [key] of fileEntries) {
|
|
341
374
|
const parts = key.split("/");
|
|
342
375
|
let current = "";
|
|
343
376
|
for (let i = 1; i < parts.length - 1; i++) {
|
|
@@ -366,8 +399,9 @@ async function handleListFolders() {
|
|
|
366
399
|
async function handleCountImages() {
|
|
367
400
|
try {
|
|
368
401
|
const meta = await loadMeta();
|
|
402
|
+
const fileEntries = getFileEntries(meta);
|
|
369
403
|
const allImages = [];
|
|
370
|
-
for (const key of
|
|
404
|
+
for (const [key] of fileEntries) {
|
|
371
405
|
const fileName = _path2.default.basename(key);
|
|
372
406
|
if (isImageFile(fileName)) {
|
|
373
407
|
allImages.push(key.slice(1));
|
|
@@ -391,12 +425,13 @@ async function handleFolderImages(request) {
|
|
|
391
425
|
}
|
|
392
426
|
const folders = foldersParam.split(",");
|
|
393
427
|
const meta = await loadMeta();
|
|
428
|
+
const fileEntries = getFileEntries(meta);
|
|
394
429
|
const allImages = [];
|
|
395
430
|
const prefixes = folders.map((f) => {
|
|
396
431
|
const rel = f.replace(/^public\/?/, "");
|
|
397
432
|
return rel ? `/${rel}/` : "/";
|
|
398
433
|
});
|
|
399
|
-
for (const key of
|
|
434
|
+
for (const [key] of fileEntries) {
|
|
400
435
|
const fileName = _path2.default.basename(key);
|
|
401
436
|
if (!isImageFile(fileName)) continue;
|
|
402
437
|
for (const prefix of prefixes) {
|
|
@@ -524,7 +559,7 @@ async function handleDelete(request) {
|
|
|
524
559
|
for (const key of Object.keys(meta)) {
|
|
525
560
|
if (key.startsWith(prefix) || key === imageKey) {
|
|
526
561
|
if (!meta[key].c) {
|
|
527
|
-
for (const thumbPath of
|
|
562
|
+
for (const thumbPath of _chunkMCJNUXQ6js.getAllThumbnailPaths.call(void 0, key)) {
|
|
528
563
|
const absoluteThumbPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
529
564
|
try {
|
|
530
565
|
await _fs.promises.unlink(absoluteThumbPath);
|
|
@@ -540,7 +575,7 @@ async function handleDelete(request) {
|
|
|
540
575
|
const isInImagesFolder = itemPath.startsWith("public/images/");
|
|
541
576
|
if (!isInImagesFolder && entry) {
|
|
542
577
|
if (!isPushedToCloud) {
|
|
543
|
-
for (const thumbPath of
|
|
578
|
+
for (const thumbPath of _chunkMCJNUXQ6js.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
544
579
|
const absoluteThumbPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
545
580
|
try {
|
|
546
581
|
await _fs.promises.unlink(absoluteThumbPath);
|
|
@@ -652,8 +687,8 @@ async function handleRename(request) {
|
|
|
652
687
|
const newKey = "/" + newRelativePath;
|
|
653
688
|
if (meta[oldKey]) {
|
|
654
689
|
const entry = meta[oldKey];
|
|
655
|
-
const oldThumbPaths =
|
|
656
|
-
const newThumbPaths =
|
|
690
|
+
const oldThumbPaths = _chunkMCJNUXQ6js.getAllThumbnailPaths.call(void 0, oldKey);
|
|
691
|
+
const newThumbPaths = _chunkMCJNUXQ6js.getAllThumbnailPaths.call(void 0, newKey);
|
|
657
692
|
for (let i = 0; i < oldThumbPaths.length; i++) {
|
|
658
693
|
const oldThumbPath = _path2.default.join(process.cwd(), "public", oldThumbPaths[i]);
|
|
659
694
|
const newThumbPath = _path2.default.join(process.cwd(), "public", newThumbPaths[i]);
|
|
@@ -732,8 +767,8 @@ async function handleMove(request) {
|
|
|
732
767
|
const newKey = "/" + newRelativePath;
|
|
733
768
|
if (meta[oldKey]) {
|
|
734
769
|
const entry = meta[oldKey];
|
|
735
|
-
const oldThumbPaths =
|
|
736
|
-
const newThumbPaths =
|
|
770
|
+
const oldThumbPaths = _chunkMCJNUXQ6js.getAllThumbnailPaths.call(void 0, oldKey);
|
|
771
|
+
const newThumbPaths = _chunkMCJNUXQ6js.getAllThumbnailPaths.call(void 0, newKey);
|
|
737
772
|
for (let i = 0; i < oldThumbPaths.length; i++) {
|
|
738
773
|
const oldThumbPath = _path2.default.join(process.cwd(), "public", oldThumbPaths[i]);
|
|
739
774
|
const newThumbPath = _path2.default.join(process.cwd(), "public", newThumbPaths[i]);
|
|
@@ -790,6 +825,7 @@ async function handleSync(request) {
|
|
|
790
825
|
return _server.NextResponse.json({ error: "No image keys provided" }, { status: 400 });
|
|
791
826
|
}
|
|
792
827
|
const meta = await loadMeta();
|
|
828
|
+
const cdnIndex = getOrAddCdnIndex(meta, publicUrl);
|
|
793
829
|
const r2 = new (0, _clients3.S3Client)({
|
|
794
830
|
region: "auto",
|
|
795
831
|
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
|
|
@@ -798,12 +834,12 @@ async function handleSync(request) {
|
|
|
798
834
|
const pushed = [];
|
|
799
835
|
const errors = [];
|
|
800
836
|
for (const imageKey of imageKeys) {
|
|
801
|
-
const entry = meta
|
|
837
|
+
const entry = getMetaEntry(meta, imageKey);
|
|
802
838
|
if (!entry) {
|
|
803
839
|
errors.push(`Image not found in meta: ${imageKey}. Run Scan first.`);
|
|
804
840
|
continue;
|
|
805
841
|
}
|
|
806
|
-
if (entry.c) {
|
|
842
|
+
if (entry.c !== void 0) {
|
|
807
843
|
pushed.push(imageKey);
|
|
808
844
|
continue;
|
|
809
845
|
}
|
|
@@ -827,7 +863,7 @@ async function handleSync(request) {
|
|
|
827
863
|
errors.push(`Original file not found: ${imageKey}`);
|
|
828
864
|
continue;
|
|
829
865
|
}
|
|
830
|
-
for (const thumbPath of
|
|
866
|
+
for (const thumbPath of _chunkMCJNUXQ6js.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
831
867
|
const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
832
868
|
try {
|
|
833
869
|
const fileBuffer = await _fs.promises.readFile(localPath);
|
|
@@ -842,8 +878,8 @@ async function handleSync(request) {
|
|
|
842
878
|
} catch (e20) {
|
|
843
879
|
}
|
|
844
880
|
}
|
|
845
|
-
entry.c =
|
|
846
|
-
for (const thumbPath of
|
|
881
|
+
entry.c = cdnIndex;
|
|
882
|
+
for (const thumbPath of _chunkMCJNUXQ6js.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
847
883
|
const localPath = _path2.default.join(process.cwd(), "public", thumbPath);
|
|
848
884
|
try {
|
|
849
885
|
await _fs.promises.unlink(localPath);
|
|
@@ -883,8 +919,9 @@ async function handleReprocess(request) {
|
|
|
883
919
|
for (const imageKey of imageKeys) {
|
|
884
920
|
try {
|
|
885
921
|
let buffer;
|
|
886
|
-
const entry = meta
|
|
887
|
-
const isPushedToCloud = _optionalChain([entry, 'optionalAccess', _6 => _6.c])
|
|
922
|
+
const entry = getMetaEntry(meta, imageKey);
|
|
923
|
+
const isPushedToCloud = _optionalChain([entry, 'optionalAccess', _6 => _6.c]) !== void 0;
|
|
924
|
+
const existingCdnIndex = _optionalChain([entry, 'optionalAccess', _7 => _7.c]);
|
|
888
925
|
const originalPath = _path2.default.join(process.cwd(), "public", imageKey);
|
|
889
926
|
try {
|
|
890
927
|
buffer = await _fs.promises.readFile(originalPath);
|
|
@@ -900,7 +937,7 @@ async function handleReprocess(request) {
|
|
|
900
937
|
}
|
|
901
938
|
const updatedEntry = await processImage(buffer, imageKey);
|
|
902
939
|
if (isPushedToCloud) {
|
|
903
|
-
updatedEntry.c =
|
|
940
|
+
updatedEntry.c = existingCdnIndex;
|
|
904
941
|
await uploadToCdn(imageKey);
|
|
905
942
|
await deleteLocalThumbnails(imageKey);
|
|
906
943
|
try {
|
|
@@ -942,7 +979,7 @@ async function handleProcessAllStream() {
|
|
|
942
979
|
const orphansRemoved = [];
|
|
943
980
|
let alreadyProcessed = 0;
|
|
944
981
|
const imagesToProcess = [];
|
|
945
|
-
for (const [key, entry] of
|
|
982
|
+
for (const [key, entry] of getFileEntries(meta)) {
|
|
946
983
|
const fileName = _path2.default.basename(key);
|
|
947
984
|
if (!isImageFile(fileName)) continue;
|
|
948
985
|
if (!entry.p) {
|
|
@@ -956,7 +993,8 @@ async function handleProcessAllStream() {
|
|
|
956
993
|
for (let i = 0; i < imagesToProcess.length; i++) {
|
|
957
994
|
const { key, entry } = imagesToProcess[i];
|
|
958
995
|
const fullPath = _path2.default.join(process.cwd(), "public", key);
|
|
959
|
-
const isInCloud = entry.c
|
|
996
|
+
const isInCloud = entry.c !== void 0;
|
|
997
|
+
const existingCdnIndex = entry.c;
|
|
960
998
|
sendEvent({
|
|
961
999
|
type: "progress",
|
|
962
1000
|
current: i + 1,
|
|
@@ -996,7 +1034,7 @@ async function handleProcessAllStream() {
|
|
|
996
1034
|
meta[key] = {
|
|
997
1035
|
...processedEntry,
|
|
998
1036
|
p: 1,
|
|
999
|
-
...isInCloud ? { c:
|
|
1037
|
+
...isInCloud ? { c: existingCdnIndex } : {}
|
|
1000
1038
|
};
|
|
1001
1039
|
}
|
|
1002
1040
|
if (isInCloud) {
|
|
@@ -1015,9 +1053,9 @@ async function handleProcessAllStream() {
|
|
|
1015
1053
|
}
|
|
1016
1054
|
sendEvent({ type: "cleanup", message: "Removing orphaned thumbnails..." });
|
|
1017
1055
|
const trackedPaths = /* @__PURE__ */ new Set();
|
|
1018
|
-
for (const imageKey of
|
|
1019
|
-
if (
|
|
1020
|
-
for (const thumbPath of
|
|
1056
|
+
for (const [imageKey, entry] of getFileEntries(meta)) {
|
|
1057
|
+
if (entry.c === void 0) {
|
|
1058
|
+
for (const thumbPath of _chunkMCJNUXQ6js.getAllThumbnailPaths.call(void 0, imageKey)) {
|
|
1021
1059
|
trackedPaths.add(thumbPath);
|
|
1022
1060
|
}
|
|
1023
1061
|
}
|
|
@@ -1104,6 +1142,7 @@ async function handleProcessAllStream() {
|
|
|
1104
1142
|
|
|
1105
1143
|
|
|
1106
1144
|
|
|
1145
|
+
|
|
1107
1146
|
async function handleScanStream() {
|
|
1108
1147
|
const encoder = new TextEncoder();
|
|
1109
1148
|
const stream = new ReadableStream({
|
|
@@ -1115,6 +1154,7 @@ async function handleScanStream() {
|
|
|
1115
1154
|
};
|
|
1116
1155
|
try {
|
|
1117
1156
|
const meta = await loadMeta();
|
|
1157
|
+
const existingCount = Object.keys(meta).length;
|
|
1118
1158
|
const existingKeys = new Set(Object.keys(meta));
|
|
1119
1159
|
const added = [];
|
|
1120
1160
|
const renamed = [];
|
|
@@ -1182,13 +1222,17 @@ async function handleScanStream() {
|
|
|
1182
1222
|
if (isImage) {
|
|
1183
1223
|
const ext = _path2.default.extname(relativePath).toLowerCase();
|
|
1184
1224
|
if (ext === ".svg") {
|
|
1185
|
-
meta[imageKey] = { w: 0, h: 0 };
|
|
1225
|
+
meta[imageKey] = { w: 0, h: 0, b: "" };
|
|
1186
1226
|
} else {
|
|
1187
1227
|
try {
|
|
1188
|
-
const
|
|
1228
|
+
const buffer = await _fs.promises.readFile(fullPath);
|
|
1229
|
+
const metadata = await _sharp2.default.call(void 0, buffer).metadata();
|
|
1230
|
+
const { data, info } = await _sharp2.default.call(void 0, buffer).resize(32, 32, { fit: "inside" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
1231
|
+
const blurhash = _blurhash.encode.call(void 0, new Uint8ClampedArray(data), info.width, info.height, 4, 4);
|
|
1189
1232
|
meta[imageKey] = {
|
|
1190
1233
|
w: metadata.width || 0,
|
|
1191
|
-
h: metadata.height || 0
|
|
1234
|
+
h: metadata.height || 0,
|
|
1235
|
+
b: blurhash
|
|
1192
1236
|
};
|
|
1193
1237
|
} catch (e31) {
|
|
1194
1238
|
meta[imageKey] = { w: 0, h: 0 };
|
|
@@ -1207,6 +1251,7 @@ async function handleScanStream() {
|
|
|
1207
1251
|
await saveMeta(meta);
|
|
1208
1252
|
sendEvent({
|
|
1209
1253
|
type: "complete",
|
|
1254
|
+
existingCount,
|
|
1210
1255
|
added: added.length,
|
|
1211
1256
|
renamed: renamed.length,
|
|
1212
1257
|
errors: errors.length,
|
|
@@ -1229,6 +1274,132 @@ async function handleScanStream() {
|
|
|
1229
1274
|
});
|
|
1230
1275
|
}
|
|
1231
1276
|
|
|
1277
|
+
// src/handlers/import.ts
|
|
1278
|
+
|
|
1279
|
+
|
|
1280
|
+
function parseImageUrl(url) {
|
|
1281
|
+
const parsed = new URL(url);
|
|
1282
|
+
const base = `${parsed.protocol}//${parsed.host}`;
|
|
1283
|
+
const path9 = parsed.pathname;
|
|
1284
|
+
return { base, path: path9 };
|
|
1285
|
+
}
|
|
1286
|
+
async function processRemoteImage(url) {
|
|
1287
|
+
const response = await fetch(url);
|
|
1288
|
+
if (!response.ok) {
|
|
1289
|
+
throw new Error(`Failed to fetch: ${response.status}`);
|
|
1290
|
+
}
|
|
1291
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
1292
|
+
const metadata = await _sharp2.default.call(void 0, buffer).metadata();
|
|
1293
|
+
const { data, info } = await _sharp2.default.call(void 0, buffer).resize(32, 32, { fit: "inside" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
1294
|
+
const blurhash = _blurhash.encode.call(void 0, new Uint8ClampedArray(data), info.width, info.height, 4, 4);
|
|
1295
|
+
return {
|
|
1296
|
+
w: metadata.width || 0,
|
|
1297
|
+
h: metadata.height || 0,
|
|
1298
|
+
b: blurhash
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
async function handleImportUrls(request) {
|
|
1302
|
+
const encoder = new TextEncoder();
|
|
1303
|
+
const stream = new ReadableStream({
|
|
1304
|
+
async start(controller) {
|
|
1305
|
+
const sendEvent = (data) => {
|
|
1306
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
|
|
1307
|
+
|
|
1308
|
+
`));
|
|
1309
|
+
};
|
|
1310
|
+
try {
|
|
1311
|
+
const { urls } = await request.json();
|
|
1312
|
+
if (!urls || !Array.isArray(urls) || urls.length === 0) {
|
|
1313
|
+
sendEvent({ type: "error", message: "No URLs provided" });
|
|
1314
|
+
controller.close();
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
const meta = await loadMeta();
|
|
1318
|
+
const added = [];
|
|
1319
|
+
const skipped = [];
|
|
1320
|
+
const errors = [];
|
|
1321
|
+
const total = urls.length;
|
|
1322
|
+
sendEvent({ type: "start", total });
|
|
1323
|
+
for (let i = 0; i < urls.length; i++) {
|
|
1324
|
+
const url = urls[i].trim();
|
|
1325
|
+
if (!url) continue;
|
|
1326
|
+
sendEvent({
|
|
1327
|
+
type: "progress",
|
|
1328
|
+
current: i + 1,
|
|
1329
|
+
total,
|
|
1330
|
+
percent: Math.round((i + 1) / total * 100),
|
|
1331
|
+
currentFile: url
|
|
1332
|
+
});
|
|
1333
|
+
try {
|
|
1334
|
+
const { base, path: path9 } = parseImageUrl(url);
|
|
1335
|
+
const existingEntry = getMetaEntry(meta, path9);
|
|
1336
|
+
if (existingEntry) {
|
|
1337
|
+
skipped.push(path9);
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
const cdnIndex = getOrAddCdnIndex(meta, base);
|
|
1341
|
+
const imageData = await processRemoteImage(url);
|
|
1342
|
+
setMetaEntry(meta, path9, {
|
|
1343
|
+
w: imageData.w,
|
|
1344
|
+
h: imageData.h,
|
|
1345
|
+
b: imageData.b,
|
|
1346
|
+
c: cdnIndex
|
|
1347
|
+
});
|
|
1348
|
+
added.push(path9);
|
|
1349
|
+
} catch (error) {
|
|
1350
|
+
console.error(`Failed to import ${url}:`, error);
|
|
1351
|
+
errors.push(url);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
await saveMeta(meta);
|
|
1355
|
+
sendEvent({
|
|
1356
|
+
type: "complete",
|
|
1357
|
+
added: added.length,
|
|
1358
|
+
skipped: skipped.length,
|
|
1359
|
+
errors: errors.length
|
|
1360
|
+
});
|
|
1361
|
+
} catch (error) {
|
|
1362
|
+
console.error("Import failed:", error);
|
|
1363
|
+
sendEvent({ type: "error", message: "Import failed" });
|
|
1364
|
+
} finally {
|
|
1365
|
+
controller.close();
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1369
|
+
return new Response(stream, {
|
|
1370
|
+
headers: {
|
|
1371
|
+
"Content-Type": "text/event-stream",
|
|
1372
|
+
"Cache-Control": "no-cache",
|
|
1373
|
+
"Connection": "keep-alive"
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
async function handleGetCdns() {
|
|
1378
|
+
try {
|
|
1379
|
+
const meta = await loadMeta();
|
|
1380
|
+
const cdns = meta._cdns || [];
|
|
1381
|
+
return Response.json({ cdns });
|
|
1382
|
+
} catch (error) {
|
|
1383
|
+
console.error("Failed to get CDNs:", error);
|
|
1384
|
+
return Response.json({ error: "Failed to get CDNs" }, { status: 500 });
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
async function handleUpdateCdns(request) {
|
|
1388
|
+
try {
|
|
1389
|
+
const { cdns } = await request.json();
|
|
1390
|
+
if (!Array.isArray(cdns)) {
|
|
1391
|
+
return Response.json({ error: "Invalid CDN array" }, { status: 400 });
|
|
1392
|
+
}
|
|
1393
|
+
const meta = await loadMeta();
|
|
1394
|
+
meta._cdns = cdns.map((url) => url.replace(/\/$/, ""));
|
|
1395
|
+
await saveMeta(meta);
|
|
1396
|
+
return Response.json({ success: true, cdns: meta._cdns });
|
|
1397
|
+
} catch (error) {
|
|
1398
|
+
console.error("Failed to update CDNs:", error);
|
|
1399
|
+
return Response.json({ error: "Failed to update CDNs" }, { status: 500 });
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1232
1403
|
// src/handlers/index.ts
|
|
1233
1404
|
async function GET(request) {
|
|
1234
1405
|
if (process.env.NODE_ENV !== "development") {
|
|
@@ -1251,6 +1422,9 @@ async function GET(request) {
|
|
|
1251
1422
|
if (route === "search") {
|
|
1252
1423
|
return handleSearch(request);
|
|
1253
1424
|
}
|
|
1425
|
+
if (route === "cdns") {
|
|
1426
|
+
return handleGetCdns();
|
|
1427
|
+
}
|
|
1254
1428
|
return _server.NextResponse.json({ error: "Not found" }, { status: 404 });
|
|
1255
1429
|
}
|
|
1256
1430
|
async function POST(request) {
|
|
@@ -1286,6 +1460,12 @@ async function POST(request) {
|
|
|
1286
1460
|
if (route === "scan") {
|
|
1287
1461
|
return handleScanStream();
|
|
1288
1462
|
}
|
|
1463
|
+
if (route === "import") {
|
|
1464
|
+
return handleImportUrls(request);
|
|
1465
|
+
}
|
|
1466
|
+
if (route === "cdns") {
|
|
1467
|
+
return handleUpdateCdns(request);
|
|
1468
|
+
}
|
|
1289
1469
|
return _server.NextResponse.json({ error: "Not found" }, { status: 404 });
|
|
1290
1470
|
}
|
|
1291
1471
|
async function DELETE(request) {
|