grab-url 1.0.6 → 1.0.8
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/download.cjs.js +3 -0
- package/dist/download.cjs.js.map +1 -0
- package/dist/download.d.ts +1 -0
- package/dist/download.es.js +3715 -0
- package/dist/download.es.js.map +1 -0
- package/dist/grab-api.cjs.js +1 -1
- package/dist/grab-api.cjs.js.map +1 -1
- package/dist/grab-api.d.ts +64 -55
- package/dist/grab-api.es.js +87 -223
- package/dist/grab-api.es.js.map +1 -1
- package/dist/grab-api.umd.js +2 -0
- package/dist/grab-api.umd.js.map +1 -0
- package/dist/icons.cjs.js +1 -1
- package/dist/icons.cjs.js.map +1 -1
- package/dist/icons.d.ts +0 -0
- package/dist/icons.es.js +3 -3
- package/dist/icons.es.js.map +1 -1
- package/dist/log.cjs.js +2 -0
- package/dist/log.cjs.js.map +1 -0
- package/dist/log.d.ts +123 -0
- package/dist/log.es.js +299 -0
- package/dist/log.es.js.map +1 -0
- package/package.json +17 -13
- package/readme.md +7 -7
- package/src/grab-api.ts +114 -72
- package/src/grab-url.js +15 -81
- package/src/icons/cli/spinners.js +818 -0
- package/src/icons/svg/index.ts +0 -0
- package/src/icons/svg/loading-bouncy-ball.svg +0 -0
- package/src/icons/svg/loading-double-ring.svg +0 -0
- package/src/icons/svg/loading-eclipse.svg +0 -0
- package/src/icons/svg/loading-ellipsis.svg +0 -0
- package/src/icons/svg/loading-floating-search.svg +0 -0
- package/src/icons/svg/loading-gears.svg +0 -0
- package/src/icons/svg/loading-infinity.svg +0 -0
- package/src/icons/svg/loading-orbital.svg +0 -0
- package/src/icons/svg/loading-pacman.svg +0 -0
- package/src/icons/svg/loading-pulse-bars.svg +0 -0
- package/src/icons/svg/loading-red-blue-ball.svg +0 -0
- package/src/icons/svg/loading-reload-arrow.svg +0 -0
- package/src/icons/svg/loading-ring.svg +0 -0
- package/src/icons/svg/loading-ripple.svg +0 -0
- package/src/icons/svg/loading-spinner-oval.svg +0 -0
- package/src/icons/svg/loading-spinner.svg +0 -0
- package/src/icons/svg/loading-square-blocks.svg +0 -0
- package/src/{log.ts → log-json.ts} +128 -129
- package/src/icons/cli/spinners.json +0 -1074
package/src/grab-api.ts
CHANGED
|
@@ -1,29 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
printJSONStructure,
|
|
3
3
|
log,
|
|
4
|
-
showAlert,
|
|
5
|
-
setupDevTools,
|
|
6
4
|
type LogOptions,
|
|
7
|
-
} from "./log";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* TODO
|
|
11
|
-
* - react tests
|
|
12
|
-
* - grab error popup and dev tool
|
|
13
|
-
* - show net log in alert
|
|
14
|
-
* - progress
|
|
15
|
-
* - pagination working
|
|
16
|
-
* - tests in stackblitz
|
|
17
|
-
* - loading icons
|
|
18
|
-
* - cache revalidation
|
|
19
|
-
*/
|
|
5
|
+
} from "./log-json";
|
|
20
6
|
|
|
21
7
|
/**
|
|
22
8
|
* ### GRAB: Generate Request to API from Browser
|
|
23
|
-
* 
|
|
24
10
|
*
|
|
25
11
|
* 1. **GRAB is the FBEST Request Manager: Functionally Brilliant, Elegantly Simple Tool**: One Function, no dependencies,
|
|
26
|
-
* minimalist syntax, [more features than alternatives](https://grab.js.org/
|
|
12
|
+
* minimalist syntax, [more features than alternatives](https://grab.js.org/docs/Comparisons)
|
|
27
13
|
* 2. **Auto-JSON Convert**: Pass parameters and get response or error in JSON, handling other data types as is.
|
|
28
14
|
* 3. **isLoading Status**: Sets `.isLoading=true` on the pre-initialized response object so you can show a "Loading..." in any framework
|
|
29
15
|
* 4. **Debug Logging**: Adds global `log()` and prints colored JSON structure, response, timing for requests in test.
|
|
@@ -39,7 +25,7 @@ import {
|
|
|
39
25
|
* 14. **Framework Agnostic**: Alternatives like TanStack work only in component initialization and depend on React & others.
|
|
40
26
|
* 15. **Globals**: Adds to window in browser or global in Node.js so you only import once: `grab()`, `log()`, `grab.log`, `grab.mock`, `grab.defaults`
|
|
41
27
|
* 16. **TypeScript Tooltips**: Developers can hover over option names and autocomplete TypeScript.
|
|
42
|
-
* 17. **Request Stategies**: [🎯 Examples](https://grab.js.org/
|
|
28
|
+
* 17. **Request Stategies**: [🎯 Examples](https://grab.js.org/docs/Examples) show common stategies like debounce, repeat, proxy, unit tests, interceptors, file upload, etc
|
|
43
29
|
* 18. **Rate Limiting**: Built-in rate limiting to prevent multi-click cascading responses, require to wait seconds between requests.
|
|
44
30
|
* 19. **Repeat**: Repeat request this many times, or repeat every X seconds to poll for updates.
|
|
45
31
|
* 20. **Loading Icons**: Import from `grab-url/icons` to get enhanced animated loading icons.
|
|
@@ -77,33 +63,27 @@ import {
|
|
|
77
63
|
* @param {any} [...params] All other params become GET params, POST body, and other methods.
|
|
78
64
|
* @returns {Promise<Object>} The response object with resulting data or .error if error.
|
|
79
65
|
* @author [vtempest (2025)](https://github.com/vtempest/GRAB-URL)
|
|
80
|
-
* @see
|
|
81
|
-
* @example import grab from 'grab-url';
|
|
82
|
-
* let res = {};
|
|
83
|
-
* await grab('search', {
|
|
84
|
-
* response: res,
|
|
85
|
-
* query: "search words"
|
|
86
|
-
* })
|
|
66
|
+
* @see [🎯 Examples](https://grab.js.org/docs/Examples) [📑 Docs](https://grab.js.org)
|
|
87
67
|
*/
|
|
88
68
|
export default async function grab<TResponse = any, TParams = any>(
|
|
89
69
|
path: string,
|
|
90
70
|
options: GrabOptions<TResponse, TParams>
|
|
91
71
|
): Promise<GrabResponse<TResponse>> {
|
|
92
|
-
|
|
72
|
+
var {
|
|
93
73
|
headers,
|
|
94
74
|
response = {} as any, // Pre-initialized object to set the response in. isLoading and error are also set on this object.
|
|
95
75
|
method = options.post // set post: true for POST, omit for GET
|
|
96
76
|
? "POST"
|
|
97
77
|
: options.put
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
78
|
+
? "PUT"
|
|
79
|
+
: options.patch
|
|
80
|
+
? "PATCH"
|
|
81
|
+
: "GET",
|
|
102
82
|
cache = false, // Enable/disable frontend caching
|
|
103
83
|
cacheForTime = 60, // Seconds to consider data stale and invalidate cache
|
|
104
84
|
timeout = 30, // Request timeout in seconds
|
|
105
85
|
baseURL = (typeof process !== "undefined" && process.env.SERVER_API_URL) ||
|
|
106
|
-
|
|
86
|
+
"/api/", // Use env var or default to /api/
|
|
107
87
|
cancelOngoingIfNew = false, // Cancel previous request for same path
|
|
108
88
|
cancelNewIfOngoing = false, // Don't make new request if one is ongoing
|
|
109
89
|
rateLimit = 0, // Minimum seconds between requests
|
|
@@ -127,7 +107,7 @@ export default async function grab<TResponse = any, TParams = any>(
|
|
|
127
107
|
put = false,
|
|
128
108
|
patch = false,
|
|
129
109
|
body = null,
|
|
130
|
-
...params
|
|
110
|
+
...params // All other params become request params/query
|
|
131
111
|
} = {
|
|
132
112
|
// Destructure options with defaults, merging with any globally set defaults
|
|
133
113
|
...(typeof window !== "undefined"
|
|
@@ -181,7 +161,7 @@ export default async function grab<TResponse = any, TParams = any>(
|
|
|
181
161
|
}
|
|
182
162
|
|
|
183
163
|
// regrab on stale, on window refocus, on network
|
|
184
|
-
if (typeof window !== undefined) {
|
|
164
|
+
if (typeof window !== "undefined") {
|
|
185
165
|
const regrab = async () => await grab(path, { ...options, cache: false });
|
|
186
166
|
if (regrabOnStale && cache) setTimeout(regrab, 1000 * cacheForTime);
|
|
187
167
|
if (regrabOnNetwork) window.addEventListener("online", regrab);
|
|
@@ -202,19 +182,23 @@ export default async function grab<TResponse = any, TParams = any>(
|
|
|
202
182
|
|
|
203
183
|
// Configure infinite scroll behavior if enabled
|
|
204
184
|
// Attaches scroll listener to specified element that triggers next page load
|
|
205
|
-
if (infiniteScroll?.length && typeof
|
|
185
|
+
if (infiniteScroll?.length && typeof paginateElement !== "undefined"
|
|
186
|
+
&& typeof window !== "undefined") {
|
|
206
187
|
let paginateDOM =
|
|
207
188
|
typeof paginateElement === "string"
|
|
208
189
|
? document.querySelector(paginateElement)
|
|
209
190
|
: paginateElement;
|
|
210
191
|
|
|
211
|
-
if (
|
|
192
|
+
if (!paginateDOM) log("paginateDOM not found", { color: "red" });
|
|
193
|
+
else if (window.scrollListener
|
|
194
|
+
&& typeof paginateDOM !== "undefined"
|
|
195
|
+
&& typeof paginateDOM.removeEventListener === "function")
|
|
212
196
|
paginateDOM.removeEventListener("scroll", window.scrollListener);
|
|
213
197
|
|
|
214
198
|
// Your modified scroll listener with position saving
|
|
215
199
|
window.scrollListener = async (event) => {
|
|
216
200
|
const t = event.target as HTMLElement;
|
|
217
|
-
|
|
201
|
+
|
|
218
202
|
// Save scroll position whenever user scrolls
|
|
219
203
|
localStorage.setItem(
|
|
220
204
|
"scroll",
|
|
@@ -230,7 +214,8 @@ export default async function grab<TResponse = any, TParams = any>(
|
|
|
230
214
|
}
|
|
231
215
|
};
|
|
232
216
|
|
|
233
|
-
paginateDOM
|
|
217
|
+
if (paginateDOM)
|
|
218
|
+
paginateDOM.addEventListener("scroll", window.scrollListener);
|
|
234
219
|
}
|
|
235
220
|
|
|
236
221
|
// Check request history for a previous request with same path/params
|
|
@@ -276,8 +261,7 @@ export default async function grab<TResponse = any, TParams = any>(
|
|
|
276
261
|
|
|
277
262
|
// Update page tracking
|
|
278
263
|
if (priorRequest) priorRequest.currentPage = pageNumber;
|
|
279
|
-
|
|
280
|
-
params[paginateKey] = pageNumber;
|
|
264
|
+
params = { ...params, [paginateKey]: pageNumber };
|
|
281
265
|
}
|
|
282
266
|
|
|
283
267
|
// Set loading state on response object
|
|
@@ -373,7 +357,7 @@ export default async function grab<TResponse = any, TParams = any>(
|
|
|
373
357
|
// Make actual API request and handle response based on content type
|
|
374
358
|
res = await fetch(baseURL + path + paramsGETRequest, fetchParams).catch(
|
|
375
359
|
(e) => {
|
|
376
|
-
throw new Error(e);
|
|
360
|
+
throw new Error(e.message);
|
|
377
361
|
}
|
|
378
362
|
);
|
|
379
363
|
|
|
@@ -390,8 +374,8 @@ export default async function grab<TResponse = any, TParams = any>(
|
|
|
390
374
|
? res && res.json()
|
|
391
375
|
: type.includes("application/pdf") ||
|
|
392
376
|
type.includes("application/octet-stream")
|
|
393
|
-
|
|
394
|
-
|
|
377
|
+
? res.blob()
|
|
378
|
+
: res.text()
|
|
395
379
|
: res.json()
|
|
396
380
|
).catch((e) => {
|
|
397
381
|
throw new Error("Error parsing response: " + e);
|
|
@@ -423,15 +407,15 @@ export default async function grab<TResponse = any, TParams = any>(
|
|
|
423
407
|
if (debug) {
|
|
424
408
|
logger(
|
|
425
409
|
"Path:" +
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
410
|
+
baseURL +
|
|
411
|
+
path +
|
|
412
|
+
paramsGETRequest +
|
|
413
|
+
"\n" +
|
|
414
|
+
JSON.stringify(options, null, 2) +
|
|
415
|
+
"\nTime: " +
|
|
416
|
+
elapsedTime +
|
|
417
|
+
"s\nResponse: " +
|
|
418
|
+
printJSONStructure(res)
|
|
435
419
|
);
|
|
436
420
|
// console.log(res);
|
|
437
421
|
}
|
|
@@ -467,8 +451,9 @@ export default async function grab<TResponse = any, TParams = any>(
|
|
|
467
451
|
// Handle any errors that occurred during request processing
|
|
468
452
|
let errorMessage =
|
|
469
453
|
"Error: " + error.message + "\nPath:" + baseURL + path + "\n";
|
|
470
|
-
JSON.stringify(params);
|
|
454
|
+
// JSON.stringify(params);
|
|
471
455
|
|
|
456
|
+
// if onError hook is passed
|
|
472
457
|
if (typeof onError === "function")
|
|
473
458
|
onError(error.message, baseURL + path, params);
|
|
474
459
|
|
|
@@ -483,7 +468,7 @@ export default async function grab<TResponse = any, TParams = any>(
|
|
|
483
468
|
// Do not show errors for duplicate aborted requests
|
|
484
469
|
if (!error.message.includes("signal") && options.debug) {
|
|
485
470
|
logger(errorMessage, { color: "red" });
|
|
486
|
-
if (debug && typeof document !== undefined) showAlert(errorMessage);
|
|
471
|
+
if (debug && typeof document !== "undefined") showAlert(errorMessage);
|
|
487
472
|
}
|
|
488
473
|
response.error = error.message;
|
|
489
474
|
if (typeof response === "function") {
|
|
@@ -513,8 +498,8 @@ export default async function grab<TResponse = any, TParams = any>(
|
|
|
513
498
|
*/
|
|
514
499
|
grab.instance =
|
|
515
500
|
(defaults = {}) =>
|
|
516
|
-
|
|
517
|
-
|
|
501
|
+
(path, options = {}) =>
|
|
502
|
+
grab(path, { ...defaults, ...options });
|
|
518
503
|
|
|
519
504
|
// delays execution so that future calls may override and only executes last one
|
|
520
505
|
const debouncer = async (func, wait) => {
|
|
@@ -532,7 +517,6 @@ const debouncer = async (func, wait) => {
|
|
|
532
517
|
// Add globals to window in browser, or global in Node.js
|
|
533
518
|
if (typeof window !== "undefined") {
|
|
534
519
|
window.log = log;
|
|
535
|
-
// @ts-ignore
|
|
536
520
|
window.grab = grab;
|
|
537
521
|
|
|
538
522
|
window.grab.log = [];
|
|
@@ -564,6 +548,73 @@ if (typeof window !== "undefined") {
|
|
|
564
548
|
globalThis.grab = grab.instance();
|
|
565
549
|
}
|
|
566
550
|
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Shows message in a modal overlay with scrollable message stack
|
|
555
|
+
* and is easier to dismiss unlike alert() which blocks window.
|
|
556
|
+
* Creates a semi-transparent overlay with a white box containing the message.
|
|
557
|
+
* @param {string} msg - The message to display
|
|
558
|
+
*/
|
|
559
|
+
export function showAlert(msg) {
|
|
560
|
+
if (typeof document === "undefined") return;
|
|
561
|
+
let o = document.getElementById("alert-overlay"),
|
|
562
|
+
list;
|
|
563
|
+
|
|
564
|
+
// Create overlay and alert box if they don't exist
|
|
565
|
+
if (!o) {
|
|
566
|
+
o = document.body.appendChild(document.createElement("div"));
|
|
567
|
+
o.id = "alert-overlay";
|
|
568
|
+
o.setAttribute(
|
|
569
|
+
"style",
|
|
570
|
+
"position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center"
|
|
571
|
+
);
|
|
572
|
+
o.innerHTML = `<div id="alert-box" style="background:#fff;padding:1.5em 2em;border-radius:8px;box-shadow:0 2px 16px #0003;min-width:220px;max-height:80vh;position:relative;display:flex;flex-direction:column;">
|
|
573
|
+
<button id="close-alert" style="position:absolute;top:12px;right:20px;font-size:1.5em;background:none;border:none;cursor:pointer;color:black;">×</button>
|
|
574
|
+
<div id="alert-list" style="overflow:auto;flex:1;"></div>
|
|
575
|
+
</div>`;
|
|
576
|
+
|
|
577
|
+
// Add click handlers to close overlay
|
|
578
|
+
o.addEventListener("click", (e) => e.target == o && o.remove());
|
|
579
|
+
document.getElementById("close-alert").onclick = () => o.remove();
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
list = o.querySelector("#alert-list");
|
|
583
|
+
|
|
584
|
+
// Add new message to list
|
|
585
|
+
list.innerHTML += `<div style="border-bottom:1px solid #333; font-size:1.2em;margin:0.5em 0;">${msg}</div>`;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Sets up development tools for debugging API requests
|
|
590
|
+
* Adds a keyboard shortcut (Ctrl+Alt+I) that shows a modal with request history
|
|
591
|
+
* Each request entry shows:
|
|
592
|
+
* - Request path
|
|
593
|
+
* - Request details
|
|
594
|
+
* - Response data
|
|
595
|
+
* - Timestamp
|
|
596
|
+
*/
|
|
597
|
+
export function setupDevTools() {
|
|
598
|
+
// Keyboard shortcut (Ctrl+Alt+I) to toggle debug view
|
|
599
|
+
document.addEventListener("keydown", (e) => {
|
|
600
|
+
if (e.key === "i" && e.ctrlKey && e.altKey) {
|
|
601
|
+
// Create HTML of the grab.log requests
|
|
602
|
+
let html = " ";
|
|
603
|
+
for (let request of grab.log) {
|
|
604
|
+
html += `<div style="margin-bottom:1em; border-bottom:1px solid #ccc; padding-bottom:1em;">
|
|
605
|
+
<b>Path:</b> ${request.path}<br>
|
|
606
|
+
<b>Request:</b> ${printJSONStructure(request.request, 0, 'html')}<br>
|
|
607
|
+
<b>Response:</b> ${printJSONStructure(request.response, 0, 'html')}<br>
|
|
608
|
+
<b>Time:</b> ${new Date(request.lastFetchTime).toLocaleString()}
|
|
609
|
+
</div>`;
|
|
610
|
+
}
|
|
611
|
+
showAlert(html);
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
|
|
567
618
|
/***************** TYPESCRIPT INTERFACES *****************/
|
|
568
619
|
|
|
569
620
|
// Core response object that gets populated with API response data
|
|
@@ -602,7 +653,7 @@ export type GrabOptions<TResponse = any, TParams = any> = TParams & {
|
|
|
602
653
|
/** default=false Whether to log the request and response */
|
|
603
654
|
debug?: boolean;
|
|
604
655
|
/** default=null [page key, response field to concatenate, element with results] */
|
|
605
|
-
infiniteScroll?: [string, string, string];
|
|
656
|
+
infiniteScroll?: [string, string, string | HTMLElement];
|
|
606
657
|
/** default=false Pass this with options to set those options as defaults for all requests */
|
|
607
658
|
setDefaults?: boolean;
|
|
608
659
|
/** default=0 Retry failed requests this many times */
|
|
@@ -689,23 +740,23 @@ export interface GrabGlobal {
|
|
|
689
740
|
export interface GrabFunction {
|
|
690
741
|
/**
|
|
691
742
|
* ### GRAB: Generate Request to API from Browser
|
|
692
|
-
* 
|
|
693
744
|
* Make API request with path
|
|
694
745
|
* @returns {Promise<Object>} The response object with resulting data or .error if error.
|
|
695
746
|
* @author [vtempest (2025)](https://github.com/vtempest/GRAB-URL)
|
|
696
|
-
* @see [🎯 Examples](https://grab.js.org/
|
|
747
|
+
* @see [🎯 Examples](https://grab.js.org/docs/Examples) [📑 Docs](https://grab.js.org/lib)
|
|
697
748
|
*/
|
|
698
|
-
<TResponse = any, TParams = Record<string, any>>(path: string): Promise<
|
|
749
|
+
<TResponse = any, TParams = Record<string, any>>(path: string, options?: GrabOptions<TResponse, TParams>): Promise<
|
|
699
750
|
GrabResponse<TResponse>
|
|
700
751
|
>;
|
|
701
752
|
|
|
702
753
|
/**
|
|
703
754
|
* ### GRAB: Generate Request to API from Browser
|
|
704
|
-
* 
|
|
705
756
|
* Make API request with path and options/parameters
|
|
706
757
|
* @returns {Promise<Object>} The response object with resulting data or .error if error.
|
|
707
758
|
* @author [vtempest (2025)](https://github.com/vtempest/GRAB-URL)
|
|
708
|
-
* @see [🎯 Examples](https://grab.js.org/
|
|
759
|
+
* @see [🎯 Examples](https://grab.js.org/docs/Examples) [📑 Docs](https://grab.js.org/lib)
|
|
709
760
|
*/
|
|
710
761
|
<TResponse = any, TParams = Record<string, any>>(
|
|
711
762
|
path: string,
|
|
@@ -744,15 +795,6 @@ export interface printJSONStructureFunction {
|
|
|
744
795
|
(obj: any): string;
|
|
745
796
|
}
|
|
746
797
|
|
|
747
|
-
// Helper type for creating typed API clients
|
|
748
|
-
// export type TypedGrabFunction = <
|
|
749
|
-
// TResponse = any,
|
|
750
|
-
// TParams = Record<string, any>
|
|
751
|
-
// >(
|
|
752
|
-
// path: string,
|
|
753
|
-
// config?: GrabOptions<TResponse, TParams>
|
|
754
|
-
// ) => Promise<GrabResponse<TResponse>>;
|
|
755
|
-
|
|
756
798
|
declare global {
|
|
757
799
|
// Browser globals
|
|
758
800
|
interface Window {
|
|
@@ -774,4 +816,4 @@ declare global {
|
|
|
774
816
|
var grab: GrabFunction;
|
|
775
817
|
}
|
|
776
818
|
|
|
777
|
-
export { grab, log,
|
|
819
|
+
export { grab, log, printJSONStructure };
|
package/src/grab-url.js
CHANGED
|
@@ -4,48 +4,17 @@ import fs from 'fs';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { pipeline } from 'stream/promises';
|
|
6
6
|
import { Readable } from 'stream';
|
|
7
|
-
import cliProgress from 'cli-progress';
|
|
8
|
-
import chalk from 'chalk';
|
|
9
|
-
import ora from 'ora';
|
|
10
|
-
import Table from 'cli-table3';
|
|
11
|
-
import grab, { log } from '../dist/grab-api.es.js';
|
|
12
7
|
import readline from 'readline';
|
|
13
8
|
import { readFileSync } from 'fs';
|
|
14
|
-
import {
|
|
15
|
-
import
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// Try the icons/cli path first (where it actually exists)
|
|
24
|
-
spinners = JSON.parse(readFileSync(join(__dirname, 'icons', 'cli', 'spinners.json'), 'utf8'));
|
|
25
|
-
} catch (error) {
|
|
26
|
-
try {
|
|
27
|
-
// Try the local path
|
|
28
|
-
spinners = JSON.parse(readFileSync(join(__dirname, 'spinners.json'), 'utf8'));
|
|
29
|
-
} catch (error2) {
|
|
30
|
-
try {
|
|
31
|
-
// Try the parent directory (src)
|
|
32
|
-
spinners = JSON.parse(readFileSync(join(__dirname, '..', 'spinners.json'), 'utf8'));
|
|
33
|
-
} catch (error3) {
|
|
34
|
-
try {
|
|
35
|
-
// Try the current working directory
|
|
36
|
-
spinners = JSON.parse(readFileSync(join(process.cwd(), 'src', 'spinners.json'), 'utf8'));
|
|
37
|
-
} catch (error4) {
|
|
38
|
-
// Fallback to default spinners if file not found
|
|
39
|
-
console.warn('Could not load spinners.json, using defaults');
|
|
40
|
-
spinners = {
|
|
41
|
-
dots: { frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] },
|
|
42
|
-
line: { frames: ['-', '\\', '|', '/'] },
|
|
43
|
-
arrow: { frames: ['←', '↖', '↑', '↗', '→', '↘', '↓', '↙'] }
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import spinners from './icons/cli/spinners';
|
|
11
|
+
import grab, { log } from './grab-api';
|
|
12
|
+
import { pathToFileURL } from 'url';
|
|
13
|
+
|
|
14
|
+
import cliProgress from 'cli-progress';
|
|
15
|
+
import chalk from 'chalk';
|
|
16
|
+
// Use cli-spinners for spinner animations
|
|
17
|
+
const __dirname = dirname(import.meta.url);
|
|
49
18
|
|
|
50
19
|
// --- ArgParser from grab-cli.js ---
|
|
51
20
|
class ArgParser {
|
|
@@ -741,16 +710,14 @@ formatTotalSpeed(bytesPerSecond) {
|
|
|
741
710
|
*/
|
|
742
711
|
async downloadMultipleFiles(downloads) {
|
|
743
712
|
try {
|
|
744
|
-
console.log(this.colors.primary(`🚀 Starting download of ${downloads.length} files...\n`));
|
|
745
|
-
|
|
746
713
|
// Set up global keyboard listener for pause/resume and add URL BEFORE starting downloads
|
|
747
714
|
this.setupGlobalKeyboardListener();
|
|
748
715
|
|
|
749
716
|
// Print header row with emojis
|
|
750
|
-
this.printHeaderRow();
|
|
717
|
+
// this.printHeaderRow();
|
|
751
718
|
|
|
752
719
|
// Show keyboard shortcut info for pause/resume in multibar view
|
|
753
|
-
console.log(this.colors.info('💡
|
|
720
|
+
// console.log(this.colors.info('💡 [p] pause/resume downloads, [a] add URL.'));
|
|
754
721
|
|
|
755
722
|
// Get random colors for the multibar
|
|
756
723
|
const masterBarColor = this.getRandomBarColor();
|
|
@@ -973,7 +940,6 @@ async downloadMultipleFiles(downloads) {
|
|
|
973
940
|
const successful = results.filter(r => r.status === 'fulfilled' && r.value.success).length;
|
|
974
941
|
const failed = results.length - successful;
|
|
975
942
|
|
|
976
|
-
console.log(this.colors.green(`✅ Successful: ${successful}/${downloads.length}`));
|
|
977
943
|
if (failed > 0) {
|
|
978
944
|
console.log(this.colors.error(`❌ Failed: ${failed}/${downloads.length}`));
|
|
979
945
|
|
|
@@ -985,11 +951,11 @@ async downloadMultipleFiles(downloads) {
|
|
|
985
951
|
}
|
|
986
952
|
});
|
|
987
953
|
}
|
|
988
|
-
|
|
954
|
+
|
|
989
955
|
// Random celebration emoji
|
|
990
956
|
const celebrationEmojis = ['🥳', '🎊', '🎈', '🌟', '💯', '🚀', '✨', '🔥'];
|
|
991
957
|
const randomEmoji = celebrationEmojis[Math.floor(Math.random() * celebrationEmojis.length)];
|
|
992
|
-
console.log(this.colors.
|
|
958
|
+
console.log(this.colors.green(`${randomEmoji} Success: ${successful}/${downloads.length}`));
|
|
993
959
|
|
|
994
960
|
this.clearAbortControllers();
|
|
995
961
|
|
|
@@ -1819,7 +1785,7 @@ setupFallbackKeyboardListener() {
|
|
|
1819
1785
|
};
|
|
1820
1786
|
|
|
1821
1787
|
process.stdin.on('data', handleKeypress);
|
|
1822
|
-
console.log(this.colors.info('💡 Keyboard listener active:
|
|
1788
|
+
// console.log(this.colors.info('💡 Keyboard listener active: [p] pause/resume, [a] add URL'));
|
|
1823
1789
|
}
|
|
1824
1790
|
}
|
|
1825
1791
|
|
|
@@ -1972,22 +1938,6 @@ if (__isMain) {
|
|
|
1972
1938
|
if (i === 0 && outputFile) filename = outputFile;
|
|
1973
1939
|
return { url, outputPath: filename };
|
|
1974
1940
|
});
|
|
1975
|
-
// Show detected downloads in a table
|
|
1976
|
-
const detectedTable = new Table({
|
|
1977
|
-
head: ['#', 'URL'],
|
|
1978
|
-
colWidths: [4, 80],
|
|
1979
|
-
colAligns: ['right', 'left'],
|
|
1980
|
-
style: { 'padding-left': 1, 'padding-right': 1, head: [], border: [] }
|
|
1981
|
-
});
|
|
1982
|
-
downloads.forEach((download, index) => {
|
|
1983
|
-
detectedTable.push([
|
|
1984
|
-
(index + 1).toString(),
|
|
1985
|
-
download.url
|
|
1986
|
-
]);
|
|
1987
|
-
});
|
|
1988
|
-
console.log(chalk.cyan.bold(`Detected ${downloads.length} download(s):`));
|
|
1989
|
-
console.log(detectedTable.toString());
|
|
1990
|
-
console.log('');
|
|
1991
1941
|
// Prepare download objects with filenames
|
|
1992
1942
|
const downloadObjects = downloads.map((download, index) => {
|
|
1993
1943
|
let actualUrl = download.url;
|
|
@@ -2005,23 +1955,7 @@ if (__isMain) {
|
|
|
2005
1955
|
filename: path.basename(filename)
|
|
2006
1956
|
};
|
|
2007
1957
|
});
|
|
2008
|
-
|
|
2009
|
-
const queueTable = new Table({
|
|
2010
|
-
head: ['#', 'Filename', 'Output Path'],
|
|
2011
|
-
colWidths: [4, 32, 54],
|
|
2012
|
-
colAligns: ['right', 'left', 'left'],
|
|
2013
|
-
style: { 'padding-left': 1, 'padding-right': 1, head: [], border: [] }
|
|
2014
|
-
});
|
|
2015
|
-
downloadObjects.forEach((downloadObj, index) => {
|
|
2016
|
-
queueTable.push([
|
|
2017
|
-
(index + 1).toString(),
|
|
2018
|
-
downloadObj.filename,
|
|
2019
|
-
downloadObj.outputPath
|
|
2020
|
-
]);
|
|
2021
|
-
});
|
|
2022
|
-
console.log(chalk.cyan.bold('\nDownload Queue:'));
|
|
2023
|
-
console.log(queueTable.toString());
|
|
2024
|
-
console.log('');
|
|
1958
|
+
|
|
2025
1959
|
try {
|
|
2026
1960
|
await downloader.downloadMultipleFiles(downloadObjects);
|
|
2027
1961
|
// Display individual file stats in a table
|