@sentroy-co/client-sdk 2.4.5 → 2.5.2

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.
Files changed (41) hide show
  1. package/dist/http.d.ts +13 -0
  2. package/dist/http.d.ts.map +1 -1
  3. package/dist/http.js +62 -0
  4. package/dist/http.js.map +1 -1
  5. package/dist/react/MediaManager.d.ts +14 -0
  6. package/dist/react/MediaManager.d.ts.map +1 -1
  7. package/dist/react/MediaManager.js +21 -14
  8. package/dist/react/MediaManager.js.map +1 -1
  9. package/dist/react/crop/CropDialog.d.ts +15 -0
  10. package/dist/react/crop/CropDialog.d.ts.map +1 -0
  11. package/dist/react/crop/CropDialog.js +126 -0
  12. package/dist/react/crop/CropDialog.js.map +1 -0
  13. package/dist/react/crop/index.d.ts +7 -0
  14. package/dist/react/crop/index.d.ts.map +1 -0
  15. package/dist/react/crop/index.js +11 -0
  16. package/dist/react/crop/index.js.map +1 -0
  17. package/dist/react/index.d.ts +2 -0
  18. package/dist/react/index.d.ts.map +1 -1
  19. package/dist/react/index.js +5 -1
  20. package/dist/react/index.js.map +1 -1
  21. package/dist/react/lib/UploadQueuePanel.d.ts +20 -0
  22. package/dist/react/lib/UploadQueuePanel.d.ts.map +1 -0
  23. package/dist/react/lib/UploadQueuePanel.js +39 -0
  24. package/dist/react/lib/UploadQueuePanel.js.map +1 -0
  25. package/dist/react/lib/use-upload-queue.d.ts +51 -0
  26. package/dist/react/lib/use-upload-queue.d.ts.map +1 -0
  27. package/dist/react/lib/use-upload-queue.js +167 -0
  28. package/dist/react/lib/use-upload-queue.js.map +1 -0
  29. package/dist/resources/media.d.ts +8 -1
  30. package/dist/resources/media.d.ts.map +1 -1
  31. package/dist/resources/media.js +6 -2
  32. package/dist/resources/media.js.map +1 -1
  33. package/package.json +10 -1
  34. package/src/http.ts +85 -0
  35. package/src/react/MediaManager.tsx +41 -11
  36. package/src/react/crop/CropDialog.tsx +344 -0
  37. package/src/react/crop/index.ts +6 -0
  38. package/src/react/index.ts +10 -0
  39. package/src/react/lib/UploadQueuePanel.tsx +273 -0
  40. package/src/react/lib/use-upload-queue.ts +250 -0
  41. package/src/resources/media.ts +13 -4
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UploadQueuePanel = UploadQueuePanel;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("motion/react");
6
+ const utils_1 = require("./utils");
7
+ function UploadQueuePanel({ entries, onCancel, onRemove, onClearDone, className, }) {
8
+ if (entries.length === 0)
9
+ return null;
10
+ const active = entries.filter((e) => e.status === "queued" || e.status === "uploading").length;
11
+ const done = entries.filter((e) => e.status === "done").length;
12
+ const failed = entries.filter((e) => e.status === "error" || e.status === "canceled").length;
13
+ // Aggregate progress (toplam loaded / toplam total)
14
+ const totalLoaded = entries.reduce((s, e) => s + e.loaded, 0);
15
+ const totalSize = entries.reduce((s, e) => s + e.total, 0);
16
+ const aggPercent = totalSize > 0 ? Math.round((totalLoaded / totalSize) * 100) : 0;
17
+ return ((0, jsx_runtime_1.jsxs)(react_1.motion.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.25, ease: [0.22, 1, 0.36, 1] }, className: (0, utils_1.cn)("border-t bg-card/50 backdrop-blur-sm", className), children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between gap-3 border-b px-3 py-2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 text-xs", children: [active > 0 && ((0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-1.5 text-foreground", children: [(0, jsx_runtime_1.jsxs)("span", { className: "relative flex size-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-blue-400 opacity-75" }), (0, jsx_runtime_1.jsx)("span", { className: "relative inline-flex size-2 rounded-full bg-blue-500" })] }), active, " uploading"] })), done > 0 && ((0, jsx_runtime_1.jsxs)("span", { className: "text-emerald-600 dark:text-emerald-400", children: [done, " done"] })), failed > 0 && ((0, jsx_runtime_1.jsxs)("span", { className: "text-destructive", children: [failed, " failed"] }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 text-xs", children: [totalSize > 0 && ((0, jsx_runtime_1.jsxs)("span", { className: "tabular-nums text-muted-foreground", children: [(0, utils_1.formatBytes)(totalLoaded), " / ", (0, utils_1.formatBytes)(totalSize), " \u00B7", " ", aggPercent, "%"] })), (done > 0 || failed > 0) && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: onClearDone, className: "rounded-md border px-2 py-0.5 text-[10px] hover:bg-muted/50", children: "Clear" }))] })] }), (0, jsx_runtime_1.jsx)("div", { className: "max-h-48 overflow-y-auto py-1", children: (0, jsx_runtime_1.jsx)(react_1.AnimatePresence, { initial: false, children: entries.map((entry, i) => ((0, jsx_runtime_1.jsx)(UploadRow, { entry: entry, index: i, onCancel: () => onCancel(entry.id), onRemove: () => onRemove(entry.id) }, entry.id))) }) })] }));
18
+ }
19
+ function UploadRow({ entry, index, onCancel, onRemove, }) {
20
+ const percent = entry.total > 0 ? Math.round((entry.loaded / entry.total) * 100) : 0;
21
+ const isTerminal = entry.status === "done" ||
22
+ entry.status === "error" ||
23
+ entry.status === "canceled";
24
+ return ((0, jsx_runtime_1.jsxs)(react_1.motion.div, { layout: true, initial: { opacity: 0, x: -8 }, animate: { opacity: 1, x: 0 }, exit: { opacity: 0, height: 0 }, transition: {
25
+ duration: 0.22,
26
+ ease: [0.22, 1, 0.36, 1],
27
+ delay: index < 5 ? index * 0.04 : 0,
28
+ }, className: (0, utils_1.cn)("flex items-center gap-3 px-3 py-2", entry.status === "error" && "bg-destructive/5"), children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex size-7 shrink-0 items-center justify-center", children: [entry.status === "uploading" && ((0, jsx_runtime_1.jsx)(CircularProgress, { percent: percent })), entry.status === "queued" && ((0, jsx_runtime_1.jsx)("span", { className: "size-2 animate-pulse rounded-full bg-muted-foreground/40" })), entry.status === "done" && (0, jsx_runtime_1.jsx)(CheckmarkAnim, {}), entry.status === "error" && ((0, jsx_runtime_1.jsx)("span", { className: "text-base text-destructive", children: "!" })), entry.status === "canceled" && ((0, jsx_runtime_1.jsx)("span", { className: "text-xs text-muted-foreground", children: "\u00D7" }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col", children: [(0, jsx_runtime_1.jsx)("span", { className: "truncate text-xs font-medium", title: entry.file.name, children: entry.file.name }), (0, jsx_runtime_1.jsxs)("span", { className: "text-[10px] tabular-nums text-muted-foreground", children: [entry.status === "uploading" && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, utils_1.formatBytes)(entry.loaded), " / ", (0, utils_1.formatBytes)(entry.total), " \u00B7", " ", percent, "%"] })), entry.status === "queued" && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, utils_1.formatBytes)(entry.total), " \u00B7 queued"] })), entry.status === "done" && ((0, jsx_runtime_1.jsxs)("span", { className: "text-emerald-600 dark:text-emerald-400", children: ["uploaded \u00B7 ", (0, utils_1.formatBytes)(entry.total)] })), entry.status === "error" && ((0, jsx_runtime_1.jsx)("span", { className: "text-destructive", children: entry.error ?? "failed" })), entry.status === "canceled" && (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: "canceled" })] })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: isTerminal ? onRemove : onCancel, className: "rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground", "aria-label": isTerminal ? "Remove from list" : "Cancel upload", children: (0, jsx_runtime_1.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "size-3.5", children: (0, jsx_runtime_1.jsx)("path", { d: "M18 6 6 18M6 6l12 12" }) }) })] }));
29
+ }
30
+ function CircularProgress({ percent }) {
31
+ const radius = 10;
32
+ const circumference = 2 * Math.PI * radius;
33
+ const offset = circumference - (percent / 100) * circumference;
34
+ return ((0, jsx_runtime_1.jsxs)("svg", { viewBox: "0 0 24 24", className: "size-6 -rotate-90", children: [(0, jsx_runtime_1.jsx)("circle", { cx: "12", cy: "12", r: radius, fill: "none", stroke: "currentColor", strokeWidth: "2", className: "text-muted/40" }), (0, jsx_runtime_1.jsx)(react_1.motion.circle, { cx: "12", cy: "12", r: radius, fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeDasharray: circumference, initial: false, animate: { strokeDashoffset: offset }, transition: { duration: 0.3, ease: "easeOut" }, className: "text-blue-500" })] }));
35
+ }
36
+ function CheckmarkAnim() {
37
+ return ((0, jsx_runtime_1.jsx)(react_1.motion.svg, { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", className: "size-5 text-emerald-500", initial: { scale: 0.6, opacity: 0 }, animate: { scale: 1, opacity: 1 }, transition: { duration: 0.3, ease: [0.34, 1.56, 0.64, 1] }, children: (0, jsx_runtime_1.jsx)(react_1.motion.path, { d: "M20 6 9 17l-5-5", initial: { pathLength: 0 }, animate: { pathLength: 1 }, transition: { duration: 0.4, ease: "easeOut", delay: 0.05 } }) }));
38
+ }
39
+ //# sourceMappingURL=UploadQueuePanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UploadQueuePanel.js","sourceRoot":"","sources":["../../../src/react/lib/UploadQueuePanel.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import type { Sentroy } from "../..";
2
+ import type { Media } from "../../types";
3
+ /**
4
+ * Tek dosyalık upload entry — UI tarafında queue listesi olarak render edilir.
5
+ *
6
+ * State machine:
7
+ * queued → uploading → done | error | canceled
8
+ * `error` ve `canceled` terminal; `done` terminal + `media` set olur.
9
+ */
10
+ export interface UploadEntry {
11
+ id: string;
12
+ file: File;
13
+ status: "queued" | "uploading" | "done" | "error" | "canceled";
14
+ loaded: number;
15
+ total: number;
16
+ /** Server response — done iken non-null. */
17
+ media?: Media;
18
+ error?: string;
19
+ /** Per-entry cancel handle (XHR abort'u tetikler). */
20
+ cancel: () => void;
21
+ }
22
+ export interface UseUploadQueueOptions {
23
+ /** Aynı anda kaç dosya yüklensin. Default 3. */
24
+ concurrency?: number;
25
+ /** Dosya başarıyla yüklendiğinde tetiklenir — caller refresh edebilir. */
26
+ onUploaded?: (media: Media) => void;
27
+ }
28
+ export interface UseUploadQueueResult {
29
+ entries: UploadEntry[];
30
+ /** Yeni dosyaları queue'ya ekler ve worker'ı tetikler. */
31
+ enqueue: (bucketSlug: string, files: File[]) => void;
32
+ /** Bir entry'i iptal eder (uploading ise XHR abort, queued ise drop). */
33
+ cancel: (id: string) => void;
34
+ /** Tek bir entry'i listeden temizler (terminal state'tekiler için). */
35
+ remove: (id: string) => void;
36
+ /** Done/error/canceled olanları listeden temizler. */
37
+ clearDone: () => void;
38
+ /** Aktif (queued + uploading) entry sayısı. */
39
+ activeCount: number;
40
+ }
41
+ /**
42
+ * Upload queue hook — concurrency-pooled, per-entry progress + cancel.
43
+ *
44
+ * Storage app'in `apps/storage/lib/upload-client.ts` + `FileUploader` queue
45
+ * davranışını SDK'ya taşır. SDK consumer'ı (MediaManager veya 3rd-party)
46
+ * `entries` array'ini render eder, `enqueue`/`cancel`/`remove` çağırır.
47
+ *
48
+ * onUploaded: caller refresh için kullanır (örn `setRefreshKey + 1`).
49
+ */
50
+ export declare function useUploadQueue(client: Sentroy, opts?: UseUploadQueueOptions): UseUploadQueueResult;
51
+ //# sourceMappingURL=use-upload-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-upload-queue.d.ts","sourceRoot":"","sources":["../../../src/react/lib/use-upload-queue.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AACpC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAExC;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,IAAI,CAAA;IACV,MAAM,EAAE,QAAQ,GAAG,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAA;IAC9D,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,4CAA4C;IAC5C,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,sDAAsD;IACtD,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACpC;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,WAAW,EAAE,CAAA;IACtB,0DAA0D;IAC1D,OAAO,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAA;IACpD,yEAAyE;IACzE,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5B,uEAAuE;IACvE,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5B,sDAAsD;IACtD,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAA;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,OAAO,EACf,IAAI,GAAE,qBAA0B,GAC/B,oBAAoB,CAgMtB"}
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useUploadQueue = useUploadQueue;
4
+ const react_1 = require("react");
5
+ /**
6
+ * Upload queue hook — concurrency-pooled, per-entry progress + cancel.
7
+ *
8
+ * Storage app'in `apps/storage/lib/upload-client.ts` + `FileUploader` queue
9
+ * davranışını SDK'ya taşır. SDK consumer'ı (MediaManager veya 3rd-party)
10
+ * `entries` array'ini render eder, `enqueue`/`cancel`/`remove` çağırır.
11
+ *
12
+ * onUploaded: caller refresh için kullanır (örn `setRefreshKey + 1`).
13
+ */
14
+ function useUploadQueue(client, opts = {}) {
15
+ const concurrency = opts.concurrency ?? 3;
16
+ const [entries, setEntries] = (0, react_1.useState)([]);
17
+ const entriesRef = (0, react_1.useRef)([]);
18
+ entriesRef.current = entries;
19
+ const onUploadedRef = (0, react_1.useRef)(opts.onUploaded);
20
+ onUploadedRef.current = opts.onUploaded;
21
+ // Worker pump — queued entry varsa ve aktif < concurrency ise başlat.
22
+ const pumpRef = (0, react_1.useRef)(() => { });
23
+ /**
24
+ * Aktif (in-flight) upload sayısını synchronous olarak takip eden ref.
25
+ * `entries` state setEntries ile async güncellendiğinden, recursive
26
+ * `pump` çağrılarında `entries.filter(e => e.status === "uploading")`
27
+ * eski listeyi görür → aynı queued entry birden çok kez başlatılır
28
+ * (Chrome side `ERR_INSUFFICIENT_RESOURCES`). Ref ile incre/decre
29
+ * synchronous; concurrency limiti gerçekten devreye girer.
30
+ */
31
+ const inFlightRef = (0, react_1.useRef)(0);
32
+ /**
33
+ * Henüz başlatılmamış queued entry id'lerinin sıralı listesi. setEntries
34
+ * async olduğu için listeden seçim yapmak yarış koşulu üretir; ref
35
+ * üzerinden FIFO push/shift hem deterministik hem hızlı.
36
+ */
37
+ const queueRef = (0, react_1.useRef)([]);
38
+ const updateEntry = (0, react_1.useCallback)((id, patch) => {
39
+ setEntries((prev) => prev.map((e) => (e.id === id ? { ...e, ...patch } : e)));
40
+ }, []);
41
+ pumpRef.current = () => {
42
+ // Slot doluysa veya queue boşsa erken dön.
43
+ if (inFlightRef.current >= concurrency)
44
+ return;
45
+ const nextId = queueRef.current.shift();
46
+ if (!nextId)
47
+ return;
48
+ // Slot'u synchronously rezerve et — bir sonraki pump çağrısı bu
49
+ // entry'i tekrar shift edemez.
50
+ inFlightRef.current++;
51
+ const entry = entriesRef.current.find((e) => e.id === nextId);
52
+ if (!entry) {
53
+ // Cancel öncesi remove edilmiş; slot'u iade et ve pump'a devam.
54
+ inFlightRef.current--;
55
+ pumpRef.current?.();
56
+ return;
57
+ }
58
+ // Mark uploading
59
+ updateEntry(entry.id, { status: "uploading" });
60
+ const bucketSlug = bucketMapRef.current[entry.id];
61
+ if (!bucketSlug) {
62
+ updateEntry(entry.id, { status: "error", error: "No bucket" });
63
+ inFlightRef.current--;
64
+ pumpRef.current?.();
65
+ return;
66
+ }
67
+ const controller = new AbortController();
68
+ cancelMapRef.current[entry.id] = () => controller.abort();
69
+ client.media
70
+ .upload(bucketSlug, { body: entry.file, filename: entry.file.name }, {
71
+ onProgress: (loaded, total) => {
72
+ updateEntry(entry.id, { loaded, total });
73
+ },
74
+ signal: controller.signal,
75
+ })
76
+ .then((media) => {
77
+ updateEntry(entry.id, {
78
+ status: "done",
79
+ media,
80
+ loaded: entry.file.size,
81
+ total: entry.file.size,
82
+ });
83
+ onUploadedRef.current?.(media);
84
+ })
85
+ .catch((err) => {
86
+ const aborted = err?.message === "Upload aborted";
87
+ updateEntry(entry.id, {
88
+ status: aborted ? "canceled" : "error",
89
+ error: aborted
90
+ ? undefined
91
+ : (err?.message ?? "Upload failed"),
92
+ });
93
+ })
94
+ .finally(() => {
95
+ delete cancelMapRef.current[entry.id];
96
+ inFlightRef.current--;
97
+ // Slot açıldı, sıradakini başlat.
98
+ pumpRef.current?.();
99
+ });
100
+ // Aynı tick'te kalan slot'ları doldur — concurrency artık
101
+ // synchronously inFlightRef ile guard'lı, çift başlatma yok.
102
+ if (inFlightRef.current < concurrency)
103
+ pumpRef.current?.();
104
+ };
105
+ const bucketMapRef = (0, react_1.useRef)({});
106
+ const cancelMapRef = (0, react_1.useRef)({});
107
+ const enqueue = (0, react_1.useCallback)((bucketSlug, files) => {
108
+ if (files.length === 0)
109
+ return;
110
+ const newEntries = files.map((file) => {
111
+ const id = `up-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
112
+ bucketMapRef.current[id] = bucketSlug;
113
+ // FIFO queue — pump bu sıradan shift eder; setEntries async
114
+ // güncellemesinden bağımsız synchronous source-of-truth.
115
+ queueRef.current.push(id);
116
+ return {
117
+ id,
118
+ file,
119
+ status: "queued",
120
+ loaded: 0,
121
+ total: file.size,
122
+ cancel: () => {
123
+ const c = cancelMapRef.current[id];
124
+ if (c)
125
+ c();
126
+ else {
127
+ queueRef.current = queueRef.current.filter((qid) => qid !== id);
128
+ setEntries((prev) => prev.map((e) => e.id === id && e.status === "queued"
129
+ ? { ...e, status: "canceled" }
130
+ : e));
131
+ }
132
+ },
133
+ };
134
+ });
135
+ setEntries((prev) => [...prev, ...newEntries]);
136
+ // pump on next tick — state update'ten sonra entriesRef güncel olsun.
137
+ // pumpRef kendi içinde sequential pump zincirini sürdürür (her
138
+ // başarılı slot rezervasyonundan sonra bir dahaki pump'ı çağırır),
139
+ // dolayısıyla burada tek tetikleme yeterli.
140
+ Promise.resolve().then(() => pumpRef.current?.());
141
+ }, []);
142
+ const cancel = (0, react_1.useCallback)((id) => {
143
+ const c = cancelMapRef.current[id];
144
+ if (c)
145
+ c();
146
+ else {
147
+ queueRef.current = queueRef.current.filter((qid) => qid !== id);
148
+ setEntries((prev) => prev.map((e) => e.id === id && e.status === "queued"
149
+ ? { ...e, status: "canceled" }
150
+ : e));
151
+ }
152
+ }, []);
153
+ const remove = (0, react_1.useCallback)((id) => {
154
+ setEntries((prev) => prev.filter((e) => e.id !== id));
155
+ queueRef.current = queueRef.current.filter((qid) => qid !== id);
156
+ delete bucketMapRef.current[id];
157
+ delete cancelMapRef.current[id];
158
+ }, []);
159
+ const clearDone = (0, react_1.useCallback)(() => {
160
+ setEntries((prev) => prev.filter((e) => e.status !== "done" &&
161
+ e.status !== "error" &&
162
+ e.status !== "canceled"));
163
+ }, []);
164
+ const activeCount = entries.filter((e) => e.status === "queued" || e.status === "uploading").length;
165
+ return { entries, enqueue, cancel, remove, clearDone, activeCount };
166
+ }
167
+ //# sourceMappingURL=use-upload-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-upload-queue.js","sourceRoot":"","sources":["../../../src/react/lib/use-upload-queue.ts"],"names":[],"mappings":"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"}
@@ -29,7 +29,14 @@ export declare class MediaResource {
29
29
  * })
30
30
  * ```
31
31
  */
32
- upload(bucketSlug: string, params: UploadMediaParams): Promise<Media>;
32
+ upload(bucketSlug: string, params: UploadMediaParams, opts?: {
33
+ /** Bytes loaded / total — XHR `upload.onprogress` event'inden gelir.
34
+ * Verilmesse fetch tabanlı path kullanılır (progress yok). */
35
+ onProgress?: (loaded: number, total: number) => void;
36
+ /** AbortController.signal — kullanıcı iptal ettiğinde XHR cancel
37
+ * edilir, promise reject. */
38
+ signal?: AbortSignal;
39
+ }): Promise<Media>;
33
40
  /**
34
41
  * Delete a file. Cascades through the CDN: S3 objects (original +
35
42
  * thumbnails) are removed, then the Media record. If any S3 delete
@@ -1 +1 @@
1
- {"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../src/resources/media.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACzC,OAAO,KAAK,EACV,KAAK,EACL,eAAe,EACf,eAAe,EACf,iBAAiB,EAClB,MAAM,UAAU,CAAA;AAEjB,qBAAa,aAAa;IACZ,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,UAAU;IAEpC,0CAA0C;IACpC,IAAI,CACR,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,eAAe,GACvB,OAAO,CAAC,eAAe,CAAC;IAO3B,iCAAiC;IAC3B,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAM9D;;;;;;;;;;;;;;;;;;;;;OAqBG;IACG,MAAM,CACV,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,KAAK,CAAC;IAqBjB;;;;OAIG;IACG,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMhE;;;;;;;;OAQG;IACG,QAAQ,CACZ,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;KAAE,GACvC,OAAO,CAAC,IAAI,CAAC;CAejB"}
1
+ {"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../src/resources/media.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACzC,OAAO,KAAK,EACV,KAAK,EACL,eAAe,EACf,eAAe,EACf,iBAAiB,EAClB,MAAM,UAAU,CAAA;AAEjB,qBAAa,aAAa;IACZ,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,UAAU;IAEpC,0CAA0C;IACpC,IAAI,CACR,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,eAAe,GACvB,OAAO,CAAC,eAAe,CAAC;IAO3B,iCAAiC;IAC3B,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAM9D;;;;;;;;;;;;;;;;;;;;;OAqBG;IACG,MAAM,CACV,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,iBAAiB,EACzB,IAAI,CAAC,EAAE;QACL;uEAC+D;QAC/D,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;QACpD;sCAC8B;QAC9B,MAAM,CAAC,EAAE,WAAW,CAAA;KACrB,GACA,OAAO,CAAC,KAAK,CAAC;IAsBjB;;;;OAIG;IACG,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMhE;;;;;;;;OAQG;IACG,QAAQ,CACZ,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;KAAE,GACvC,OAAO,CAAC,IAAI,CAAC;CAejB"}
@@ -36,7 +36,7 @@ class MediaResource {
36
36
  * })
37
37
  * ```
38
38
  */
39
- async upload(bucketSlug, params) {
39
+ async upload(bucketSlug, params, opts) {
40
40
  const form = new FormData();
41
41
  const filename = params.filename ||
42
42
  params.body.name ||
@@ -53,7 +53,11 @@ class MediaResource {
53
53
  form.append("caption", params.caption);
54
54
  if (params.tags?.length)
55
55
  form.append("tags", params.tags.join(","));
56
- return this.http.postForm(`/buckets/${encodeURIComponent(bucketSlug)}/media`, form);
56
+ const path = `/buckets/${encodeURIComponent(bucketSlug)}/media`;
57
+ if (opts?.onProgress || opts?.signal) {
58
+ return this.http.postFormWithProgress(path, form, opts);
59
+ }
60
+ return this.http.postForm(path, form);
57
61
  }
58
62
  /**
59
63
  * Delete a file. Cascades through the CDN: S3 objects (original +
@@ -1 +1 @@
1
- {"version":3,"file":"media.js","sourceRoot":"","sources":["../../src/resources/media.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"media.js","sourceRoot":"","sources":["../../src/resources/media.ts"],"names":[],"mappings":";;;AAQA,MAAa,aAAa;IACJ;IAApB,YAAoB,IAAgB;QAAhB,SAAI,GAAJ,IAAI,CAAY;IAAG,CAAC;IAExC,0CAA0C;IAC1C,KAAK,CAAC,IAAI,CACR,UAAkB,EAClB,MAAwB;QAExB,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAClB,YAAY,kBAAkB,CAAC,UAAU,CAAC,QAAQ,EAClD,MAA6C,CAC9C,CAAA;IACH,CAAC;IAED,iCAAiC;IACjC,KAAK,CAAC,GAAG,CAAC,UAAkB,EAAE,OAAe;QAC3C,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAClB,YAAY,kBAAkB,CAAC,UAAU,CAAC,UAAU,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAClF,CAAA;IACH,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,KAAK,CAAC,MAAM,CACV,UAAkB,EAClB,MAAyB,EACzB,IAOC;QAED,MAAM,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAA;QAC3B,MAAM,QAAQ,GACZ,MAAM,CAAC,QAAQ;YACd,MAAM,CAAC,IAAiC,CAAC,IAAI;YAC9C,YAAY,CAAA;QACd,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;QAC1C,IAAI,MAAM,CAAC,MAAM;YAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;QACvD,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YAClC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;QAC3D,CAAC;QACD,IAAI,MAAM,CAAC,GAAG;YAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,CAAA;QAC9C,IAAI,MAAM,CAAC,OAAO;YAAE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAA;QAC1D,IAAI,MAAM,CAAC,IAAI,EAAE,MAAM;YAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;QAEnE,MAAM,IAAI,GAAG,YAAY,kBAAkB,CAAC,UAAU,CAAC,QAAQ,CAAA;QAC/D,IAAI,IAAI,EAAE,UAAU,IAAI,IAAI,EAAE,MAAM,EAAE,CAAC;YACrC,OAAO,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAQ,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;QAChE,CAAC;QACD,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAQ,IAAI,EAAE,IAAI,CAAC,CAAA;IAC9C,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,MAAM,CAAC,UAAkB,EAAE,OAAe;QAC9C,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CACjB,YAAY,kBAAkB,CAAC,UAAU,CAAC,UAAU,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAClF,CAAA;IACH,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,QAAQ,CACZ,UAAkB,EAClB,OAAe,EACf,IAAwC;QAExC,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,CAAA;QAC7B,MAAM,KAAK,GACT,OAAO,IAAI,OAAO,KAAK,UAAU;YAC/B,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE;YAC9B,CAAC,CAAC,SAAS,CAAA;QACf,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,CAClC,YAAY,kBAAkB,CAAC,UAAU,CAAC,UAAU,kBAAkB,CAAC,OAAO,CAAC,WAAW,EAC1F,EAAE,KAAK,EAAE,CACV,CAAA;QACD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,+BAA+B,GAAG,CAAC,MAAM,EAAE,CAAC,CAAA;QAC9D,CAAC;QACD,OAAO,GAAG,CAAC,IAAI,EAAE,CAAA;IACnB,CAAC;CACF;AAnHD,sCAmHC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentroy-co/client-sdk",
3
- "version": "2.4.5",
3
+ "version": "2.5.2",
4
4
  "description": "TypeScript SDK for the Sentroy platform — REST resources + React components.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -19,6 +19,11 @@
19
19
  "types": "./dist/types.d.ts",
20
20
  "import": "./dist/types.js",
21
21
  "require": "./dist/types.js"
22
+ },
23
+ "./react/crop": {
24
+ "types": "./dist/react/crop/index.d.ts",
25
+ "import": "./dist/react/crop/index.js",
26
+ "require": "./dist/react/crop/index.js"
22
27
  }
23
28
  },
24
29
  "files": [
@@ -71,5 +76,9 @@
71
76
  "react": "^19.0.0",
72
77
  "react-dom": "^19.0.0",
73
78
  "typescript": "^5.5.4"
79
+ },
80
+ "dependencies": {
81
+ "motion": "^12.38.0",
82
+ "react-easy-crop": "^5.5.7"
74
83
  }
75
84
  }
package/src/http.ts CHANGED
@@ -166,4 +166,89 @@ export class HttpClient {
166
166
  clearTimeout(timer)
167
167
  }
168
168
  }
169
+
170
+ /**
171
+ * XHR-tabanlı multipart upload — `fetch` progress event veremiyor, XHR
172
+ * `upload.onprogress` ile bytes loaded/total event'i tetikler. Caller
173
+ * `onProgress` ve `signal` (AbortController) verir.
174
+ *
175
+ * Cookie auth (`token` undefined) için `withCredentials = true`. Timeout
176
+ * uygulanmaz (büyük dosyalar için 30sn'de patlayıp kaybolmasın); caller
177
+ * istiyorsa `signal` ile kendi timeout'unu kurar.
178
+ */
179
+ async postFormWithProgress<T>(
180
+ path: string,
181
+ form: FormData,
182
+ opts: {
183
+ onProgress?: (loaded: number, total: number) => void
184
+ signal?: AbortSignal
185
+ } = {},
186
+ ): Promise<T> {
187
+ const url = this.buildUrl(path)
188
+ return new Promise<T>((resolve, reject) => {
189
+ const xhr = new XMLHttpRequest()
190
+ xhr.open("POST", url, true)
191
+ if (this.token) {
192
+ xhr.setRequestHeader("Authorization", `Bearer ${this.token}`)
193
+ } else {
194
+ xhr.withCredentials = true
195
+ }
196
+
197
+ if (opts.signal) {
198
+ if (opts.signal.aborted) {
199
+ reject(new SentroyError(0, null, "Upload aborted"))
200
+ return
201
+ }
202
+ opts.signal.addEventListener(
203
+ "abort",
204
+ () => {
205
+ try {
206
+ xhr.abort()
207
+ } catch {
208
+ /* noop */
209
+ }
210
+ reject(new SentroyError(0, null, "Upload aborted"))
211
+ },
212
+ { once: true },
213
+ )
214
+ }
215
+
216
+ if (opts.onProgress) {
217
+ xhr.upload.addEventListener("progress", (e) => {
218
+ if (e.lengthComputable) {
219
+ opts.onProgress!(e.loaded, e.total)
220
+ }
221
+ })
222
+ }
223
+
224
+ xhr.addEventListener("load", () => {
225
+ let json: { data?: T; error?: string } = {}
226
+ try {
227
+ json = JSON.parse(xhr.responseText) as { data?: T; error?: string }
228
+ } catch {
229
+ /* fall through with empty object */
230
+ }
231
+ if (xhr.status >= 200 && xhr.status < 300 && json.data !== undefined) {
232
+ resolve(json.data)
233
+ } else {
234
+ reject(
235
+ new SentroyError(
236
+ xhr.status,
237
+ json,
238
+ json.error || `Upload failed with status ${xhr.status}`,
239
+ ),
240
+ )
241
+ }
242
+ })
243
+
244
+ xhr.addEventListener("error", () =>
245
+ reject(new SentroyError(0, null, "Upload network error")),
246
+ )
247
+ xhr.addEventListener("timeout", () =>
248
+ reject(new SentroyError(0, null, "Upload timed out")),
249
+ )
250
+
251
+ xhr.send(form)
252
+ })
253
+ }
169
254
  }
@@ -9,6 +9,8 @@ import type { Sentroy } from ".."
9
9
  import type { Bucket, Media } from "../types"
10
10
  import { Lightbox } from "./lib/Lightbox"
11
11
  import { useMediaList } from "./lib/use-media-list"
12
+ import { useUploadQueue } from "./lib/use-upload-queue"
13
+ import { UploadQueuePanel } from "./lib/UploadQueuePanel"
12
14
  import { pickPresetThumbnailUrl } from "../thumbnails"
13
15
  import {
14
16
  cn,
@@ -97,6 +99,20 @@ export interface MediaManagerProps {
97
99
  showDetailsPane?: boolean
98
100
  /** Toolbar'da bucket selector görünsün mü (default true). */
99
101
  showBucketSelector?: boolean
102
+ /**
103
+ * Yükleme öncesi her dosya için çağrılan async hook. Caller dosyayı
104
+ * dönüştürebilir (örn. image crop), `null` döndürerek skip edebilir.
105
+ * Verilmezse dosyalar olduğu gibi yüklenir.
106
+ *
107
+ * @example Image crop
108
+ * ```ts
109
+ * preprocessFile={async (file) => {
110
+ * if (!file.type.startsWith("image/")) return file
111
+ * return await openCropDialog(file) // ya orijinal File ya cropped File
112
+ * }}
113
+ * ```
114
+ */
115
+ preprocessFile?: (file: File) => Promise<File | null>
100
116
  }
101
117
 
102
118
  const KIND_FILTERS: Array<{ value: MediaKind | "all"; label: string }> = [
@@ -126,6 +142,7 @@ export function MediaManager(props: MediaManagerProps) {
126
142
  classNames: cls = {},
127
143
  showDetailsPane = true,
128
144
  showBucketSelector = true,
145
+ preprocessFile,
129
146
  } = props
130
147
 
131
148
  // ── Bucket state ───────────────────────────────────────────────────────
@@ -237,24 +254,29 @@ export function MediaManager(props: MediaManagerProps) {
237
254
 
238
255
  // ── Upload ─────────────────────────────────────────────────────────────
239
256
  const fileInputRef = useRef<HTMLInputElement | null>(null)
240
- const [uploading, setUploading] = useState(false)
241
257
  const [dragOver, setDragOver] = useState(false)
242
258
 
259
+ // Queue: per-file progress + abort + concurrency-pooled. Pre-upload hook
260
+ // (`preprocessFile`) image crop gibi caller-side transform yapabilir;
261
+ // null dönerse dosya skip, File dönerse o swap'lenir.
262
+ const queue = useUploadQueue(client, {
263
+ concurrency: 3,
264
+ onUploaded: () => setRefreshKey((k) => k + 1),
265
+ })
266
+ const uploading = queue.activeCount > 0
267
+
243
268
  const uploadFiles = useCallback(
244
269
  async (files: FileList | File[]) => {
245
270
  if (!activeBucketSlug) return
246
- setUploading(true)
247
- try {
248
- const list = Array.from(files)
249
- for (const file of list) {
250
- await client.media.upload(activeBucketSlug, { body: file })
251
- }
252
- setRefreshKey((k) => k + 1)
253
- } finally {
254
- setUploading(false)
271
+ const incoming = Array.from(files)
272
+ const processed: File[] = []
273
+ for (const file of incoming) {
274
+ const out = preprocessFile ? await preprocessFile(file) : file
275
+ if (out) processed.push(out)
255
276
  }
277
+ if (processed.length > 0) queue.enqueue(activeBucketSlug, processed)
256
278
  },
257
- [client, activeBucketSlug],
279
+ [activeBucketSlug, queue, preprocessFile],
258
280
  )
259
281
 
260
282
  // ── Delete ─────────────────────────────────────────────────────────────
@@ -638,6 +660,14 @@ export function MediaManager(props: MediaManagerProps) {
638
660
  />
639
661
  )}
640
662
 
663
+ {/* Upload queue panel — entries varsa görünür, alttan slide-in */}
664
+ <UploadQueuePanel
665
+ entries={queue.entries}
666
+ onCancel={queue.cancel}
667
+ onRemove={queue.remove}
668
+ onClearDone={queue.clearDone}
669
+ />
670
+
641
671
  {/* Lightbox */}
642
672
  {lightboxIdx !== null && visibleItems[lightboxIdx] && (
643
673
  <Lightbox