@gallop.software/studio 0.1.24 → 0.1.26

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.
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); 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; }"use client";
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } 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; }"use client";
2
2
 
3
3
 
4
4
 
@@ -204,6 +204,110 @@ function AlertModal({
204
204
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles.footer, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: [styles.btn, styles.btnConfirm], onClick: onClose, children: buttonLabel }) })
205
205
  ] }) });
206
206
  }
207
+ var progressStyles = {
208
+ progressContainer: _react3.css`
209
+ margin-top: 16px;
210
+ `,
211
+ progressBar: _react3.css`
212
+ width: 100%;
213
+ height: 8px;
214
+ background-color: ${_chunkAY2DAS6Wjs.colors.background};
215
+ border-radius: 4px;
216
+ overflow: hidden;
217
+ margin-bottom: 12px;
218
+ `,
219
+ progressFill: _react3.css`
220
+ height: 100%;
221
+ background: linear-gradient(90deg, ${_chunkAY2DAS6Wjs.colors.primary}, ${_chunkAY2DAS6Wjs.colors.primaryHover});
222
+ border-radius: 4px;
223
+ transition: width 0.3s ease;
224
+ `,
225
+ progressText: _react3.css`
226
+ font-size: ${_chunkAY2DAS6Wjs.fontSize.sm};
227
+ color: ${_chunkAY2DAS6Wjs.colors.textSecondary};
228
+ margin: 0;
229
+ display: flex;
230
+ justify-content: space-between;
231
+ align-items: center;
232
+ `,
233
+ currentFile: _react3.css`
234
+ font-size: ${_chunkAY2DAS6Wjs.fontSize.xs};
235
+ color: ${_chunkAY2DAS6Wjs.colors.textMuted};
236
+ margin: 8px 0 0;
237
+ white-space: nowrap;
238
+ overflow: hidden;
239
+ text-overflow: ellipsis;
240
+ `
241
+ };
242
+ function ProgressModal({
243
+ title,
244
+ progress,
245
+ onClose,
246
+ onStop
247
+ }) {
248
+ const isComplete = progress.status === "complete";
249
+ const isError = progress.status === "error";
250
+ const isStopped = progress.status === "stopped";
251
+ const canClose = isComplete || isError || isStopped;
252
+ const isRunning = !canClose;
253
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles.overlay, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles.modal, onClick: (e) => e.stopPropagation(), children: [
254
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles.header, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { css: styles.title, children: title }) }),
255
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: styles.body, children: isError ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles.message, children: progress.message || "An error occurred" }) : isStopped ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "p", { css: styles.message, children: [
256
+ "Processing stopped. Processed ",
257
+ _nullishCoalesce(progress.processed, () => ( progress.current)),
258
+ " image",
259
+ (_nullishCoalesce(progress.processed, () => ( progress.current))) !== 1 ? "s" : "",
260
+ " before stopping."
261
+ ] }) : isComplete ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "p", { css: styles.message, children: [
262
+ "Processed ",
263
+ progress.processed,
264
+ " image",
265
+ progress.processed !== 1 ? "s" : "",
266
+ ".",
267
+ progress.orphansRemoved !== void 0 && progress.orphansRemoved > 0 ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
268
+ " Removed ",
269
+ progress.orphansRemoved,
270
+ " orphaned thumbnail",
271
+ progress.orphansRemoved !== 1 ? "s" : "",
272
+ "."
273
+ ] }) : null,
274
+ progress.errors !== void 0 && progress.errors > 0 ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
275
+ " ",
276
+ progress.errors,
277
+ " error",
278
+ progress.errors !== 1 ? "s" : "",
279
+ " occurred."
280
+ ] }) : null
281
+ ] }) : /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
282
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: styles.message, children: progress.status === "cleanup" ? "Cleaning up orphaned files..." : `Processing images...` }),
283
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: progressStyles.progressContainer, children: [
284
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { css: progressStyles.progressBar, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
285
+ "div",
286
+ {
287
+ css: progressStyles.progressFill,
288
+ style: { width: `${progress.percent}%` }
289
+ }
290
+ ) }),
291
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: progressStyles.progressText, children: [
292
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { children: [
293
+ progress.current,
294
+ " of ",
295
+ progress.total
296
+ ] }),
297
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { children: [
298
+ progress.percent,
299
+ "%"
300
+ ] })
301
+ ] }),
302
+ progress.currentFile && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { css: progressStyles.currentFile, title: progress.currentFile, children: progress.currentFile })
303
+ ] })
304
+ ] }) }),
305
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { css: styles.footer, children: [
306
+ isRunning && onStop && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: [styles.btn, styles.btnDanger], onClick: onStop, children: "Stop" }),
307
+ canClose && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { css: [styles.btn, styles.btnConfirm], onClick: onClose, children: "Done" })
308
+ ] })
309
+ ] }) });
310
+ }
207
311
 
208
312
  // src/components/StudioToolbar.tsx
209
313
 
@@ -346,11 +450,19 @@ var styles2 = {
346
450
  function StudioToolbar() {
347
451
  const { selectedItems, viewMode, setViewMode, clearSelection, currentPath, triggerRefresh, focusedItem } = useStudio();
348
452
  const fileInputRef = _react.useRef.call(void 0, null);
453
+ const abortControllerRef = _react.useRef.call(void 0, null);
349
454
  const [uploading, setUploading] = _react.useState.call(void 0, false);
350
455
  const [refreshing, setRefreshing] = _react.useState.call(void 0, false);
351
456
  const [processing, setProcessing] = _react.useState.call(void 0, false);
352
457
  const [showDeleteConfirm, setShowDeleteConfirm] = _react.useState.call(void 0, false);
353
458
  const [showProcessConfirm, setShowProcessConfirm] = _react.useState.call(void 0, false);
459
+ const [showProgress, setShowProgress] = _react.useState.call(void 0, false);
460
+ const [progressState, setProgressState] = _react.useState.call(void 0, {
461
+ current: 0,
462
+ total: 0,
463
+ percent: 0,
464
+ status: "processing"
465
+ });
354
466
  const [processCount, setProcessCount] = _react.useState.call(void 0, 0);
355
467
  const [processMode, setProcessMode] = _react.useState.call(void 0, "all");
356
468
  const [alertMessage, setAlertMessage] = _react.useState.call(void 0, null);
@@ -425,12 +537,12 @@ function StudioToolbar() {
425
537
  setShowProcessConfirm(true);
426
538
  } else {
427
539
  try {
428
- const response = await fetch("/api/studio/count-unprocessed");
540
+ const response = await fetch("/api/studio/count-images");
429
541
  const data = await response.json();
430
542
  if (data.count === 0) {
431
543
  setAlertMessage({
432
- title: "All Images Processed",
433
- message: "All images in the public folder have already been processed."
544
+ title: "No Images Found",
545
+ message: "No images found in the public folder to process."
434
546
  });
435
547
  return;
436
548
  }
@@ -438,10 +550,10 @@ function StudioToolbar() {
438
550
  setProcessMode("all");
439
551
  setShowProcessConfirm(true);
440
552
  } catch (error) {
441
- console.error("Failed to count unprocessed images:", error);
553
+ console.error("Failed to count images:", error);
442
554
  setAlertMessage({
443
555
  title: "Error",
444
- message: "Failed to count unprocessed images."
556
+ message: "Failed to count images."
445
557
  });
446
558
  }
447
559
  }
@@ -449,64 +561,160 @@ function StudioToolbar() {
449
561
  const handleProcessConfirm = _react.useCallback.call(void 0, async () => {
450
562
  setShowProcessConfirm(false);
451
563
  setProcessing(true);
564
+ abortControllerRef.current = new AbortController();
565
+ const signal = abortControllerRef.current.signal;
452
566
  try {
453
567
  if (processMode === "all") {
568
+ setShowProgress(true);
569
+ setProgressState({
570
+ current: 0,
571
+ total: processCount,
572
+ percent: 0,
573
+ status: "processing"
574
+ });
454
575
  const response = await fetch("/api/studio/process-all", {
455
- method: "POST"
576
+ method: "POST",
577
+ signal
456
578
  });
457
- const data = await response.json();
458
- if (response.ok) {
459
- const message = [
460
- `Processed ${_optionalChain([data, 'access', _10 => _10.processed, 'optionalAccess', _11 => _11.length]) || 0} images.`,
461
- _optionalChain([data, 'access', _12 => _12.orphansRemoved, 'optionalAccess', _13 => _13.length]) > 0 ? `Removed ${data.orphansRemoved.length} orphaned thumbnails.` : "",
462
- _optionalChain([data, 'access', _14 => _14.errors, 'optionalAccess', _15 => _15.length]) > 0 ? `${data.errors.length} errors occurred.` : ""
463
- ].filter(Boolean).join(" ");
464
- setAlertMessage({
465
- title: "Processing Complete",
466
- message
467
- });
468
- triggerRefresh();
469
- } else {
470
- setAlertMessage({
471
- title: "Processing Failed",
472
- message: data.error || "Unknown error"
473
- });
579
+ if (!response.body) {
580
+ throw new Error("No response body");
581
+ }
582
+ const reader = response.body.getReader();
583
+ const decoder = new TextDecoder();
584
+ try {
585
+ while (true) {
586
+ const { done, value } = await reader.read();
587
+ if (done) break;
588
+ if (signal.aborted) {
589
+ reader.cancel();
590
+ break;
591
+ }
592
+ const text = decoder.decode(value);
593
+ const lines = text.split("\n\n").filter((line) => line.startsWith("data: "));
594
+ for (const line of lines) {
595
+ try {
596
+ const data = JSON.parse(line.replace("data: ", ""));
597
+ if (data.type === "start") {
598
+ setProgressState((prev) => ({
599
+ ...prev,
600
+ total: data.total
601
+ }));
602
+ } else if (data.type === "progress") {
603
+ setProgressState({
604
+ current: data.current,
605
+ total: data.total,
606
+ percent: data.percent,
607
+ currentFile: data.currentFile,
608
+ status: "processing"
609
+ });
610
+ } else if (data.type === "cleanup") {
611
+ setProgressState((prev) => ({
612
+ ...prev,
613
+ status: "cleanup",
614
+ currentFile: void 0
615
+ }));
616
+ } else if (data.type === "complete") {
617
+ setProgressState({
618
+ current: data.processed,
619
+ total: data.processed,
620
+ percent: 100,
621
+ status: "complete",
622
+ processed: data.processed,
623
+ orphansRemoved: data.orphansRemoved,
624
+ errors: data.errors
625
+ });
626
+ triggerRefresh();
627
+ } else if (data.type === "error") {
628
+ setProgressState((prev) => ({
629
+ ...prev,
630
+ status: "error",
631
+ message: data.message
632
+ }));
633
+ }
634
+ } catch (e2) {
635
+ }
636
+ }
637
+ }
638
+ } catch (err) {
639
+ if (signal.aborted) {
640
+ setProgressState((prev) => ({
641
+ ...prev,
642
+ status: "stopped",
643
+ processed: prev.current
644
+ }));
645
+ triggerRefresh();
646
+ } else {
647
+ throw err;
648
+ }
474
649
  }
475
650
  } else {
651
+ setShowProgress(true);
652
+ setProgressState({
653
+ current: 0,
654
+ total: processCount,
655
+ percent: 0,
656
+ status: "processing"
657
+ });
476
658
  const selectedImageKeys = Array.from(selectedItems).filter((p) => {
477
- const ext = _optionalChain([p, 'access', _16 => _16.split, 'call', _17 => _17("."), 'access', _18 => _18.pop, 'call', _19 => _19(), 'optionalAccess', _20 => _20.toLowerCase, 'call', _21 => _21()]) || "";
659
+ const ext = _optionalChain([p, 'access', _10 => _10.split, 'call', _11 => _11("."), 'access', _12 => _12.pop, 'call', _13 => _13(), 'optionalAccess', _14 => _14.toLowerCase, 'call', _15 => _15()]) || "";
478
660
  return ["jpg", "jpeg", "png", "gif", "webp", "svg", "ico", "bmp", "tiff", "tif"].includes(ext);
479
661
  }).map((p) => p.replace(/^public\//, ""));
480
662
  const response = await fetch("/api/studio/reprocess", {
481
663
  method: "POST",
482
664
  headers: { "Content-Type": "application/json" },
483
- body: JSON.stringify({ imageKeys: selectedImageKeys })
665
+ body: JSON.stringify({ imageKeys: selectedImageKeys }),
666
+ signal
484
667
  });
485
668
  const data = await response.json();
486
669
  if (response.ok) {
487
- setAlertMessage({
488
- title: "Processing Complete",
489
- message: `Processed ${_optionalChain([data, 'access', _22 => _22.processed, 'optionalAccess', _23 => _23.length]) || 0} images.${_optionalChain([data, 'access', _24 => _24.errors, 'optionalAccess', _25 => _25.length]) > 0 ? ` ${data.errors.length} errors occurred.` : ""}`
670
+ setProgressState({
671
+ current: _optionalChain([data, 'access', _16 => _16.processed, 'optionalAccess', _17 => _17.length]) || 0,
672
+ total: _optionalChain([data, 'access', _18 => _18.processed, 'optionalAccess', _19 => _19.length]) || 0,
673
+ percent: 100,
674
+ status: "complete",
675
+ processed: _optionalChain([data, 'access', _20 => _20.processed, 'optionalAccess', _21 => _21.length]) || 0,
676
+ errors: _optionalChain([data, 'access', _22 => _22.errors, 'optionalAccess', _23 => _23.length]) || 0
490
677
  });
491
678
  clearSelection();
492
679
  triggerRefresh();
493
680
  } else {
494
- setAlertMessage({
495
- title: "Processing Failed",
681
+ setProgressState({
682
+ current: 0,
683
+ total: 0,
684
+ percent: 0,
685
+ status: "error",
496
686
  message: data.error || "Unknown error"
497
687
  });
498
688
  }
499
689
  }
500
690
  } catch (error) {
501
- console.error("Processing error:", error);
502
- setAlertMessage({
503
- title: "Processing Failed",
504
- message: "Processing failed. Check console for details."
505
- });
691
+ if (signal.aborted) {
692
+ setProgressState((prev) => ({
693
+ ...prev,
694
+ status: "stopped",
695
+ processed: prev.current
696
+ }));
697
+ triggerRefresh();
698
+ } else {
699
+ console.error("Processing error:", error);
700
+ setProgressState({
701
+ current: 0,
702
+ total: 0,
703
+ percent: 0,
704
+ status: "error",
705
+ message: "Processing failed. Check console for details."
706
+ });
707
+ }
506
708
  } finally {
507
709
  setProcessing(false);
710
+ abortControllerRef.current = null;
711
+ }
712
+ }, [processMode, processCount, selectedItems, clearSelection, triggerRefresh]);
713
+ const handleStopProcessing = _react.useCallback.call(void 0, () => {
714
+ if (abortControllerRef.current) {
715
+ abortControllerRef.current.abort();
508
716
  }
509
- }, [processMode, selectedItems, clearSelection, triggerRefresh]);
717
+ }, []);
510
718
  const handleDeleteClick = _react.useCallback.call(void 0, () => {
511
719
  if (selectedItems.size === 0) return;
512
720
  setShowDeleteConfirm(true);
@@ -563,12 +771,29 @@ function StudioToolbar() {
563
771
  ConfirmModal,
564
772
  {
565
773
  title: "Process Images",
566
- message: processMode === "all" ? `Found ${processCount} unprocessed image${processCount !== 1 ? "s" : ""} in the public folder. This will generate thumbnails and remove any orphaned files from the images folder.` : `Process ${processCount} selected image${processCount !== 1 ? "s" : ""}? This will regenerate thumbnails for these files.`,
774
+ message: processMode === "all" ? `Found ${processCount} image${processCount !== 1 ? "s" : ""} in the public folder. This will regenerate all thumbnails and remove any orphaned files from the images folder.` : `Process ${processCount} selected image${processCount !== 1 ? "s" : ""}? This will regenerate thumbnails for these files.`,
567
775
  confirmLabel: processing ? "Processing..." : "Process",
568
776
  onConfirm: handleProcessConfirm,
569
777
  onCancel: () => setShowProcessConfirm(false)
570
778
  }
571
779
  ),
780
+ showProgress && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
781
+ ProgressModal,
782
+ {
783
+ title: "Processing Images",
784
+ progress: progressState,
785
+ onStop: handleStopProcessing,
786
+ onClose: () => {
787
+ setShowProgress(false);
788
+ setProgressState({
789
+ current: 0,
790
+ total: 0,
791
+ percent: 0,
792
+ status: "processing"
793
+ });
794
+ }
795
+ }
796
+ ),
572
797
  alertMessage && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
573
798
  AlertModal,
574
799
  {
@@ -2222,4 +2447,4 @@ var StudioUI_default = StudioUI;
2222
2447
 
2223
2448
 
2224
2449
  exports.StudioUI = StudioUI; exports.default = StudioUI_default;
2225
- //# sourceMappingURL=StudioUI-YO6WPG5E.js.map
2450
+ //# sourceMappingURL=StudioUI-N7DC5H5U.js.map