duckdb-terminal 0.2.0 → 0.4.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.
- package/dist/duckdb-terminal.js +3246 -592
- package/dist/duckdb-terminal.umd.cjs +297 -14
- package/dist/{lib.d.ts → index.d.ts} +496 -19
- package/package.json +14 -15
- package/README.md +0 -576
package/dist/duckdb-terminal.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var h = (s, t, e) =>
|
|
4
|
-
import { Ghostty as
|
|
5
|
-
import * as
|
|
6
|
-
const
|
|
7
|
-
class
|
|
1
|
+
var Ot = Object.defineProperty;
|
|
2
|
+
var Ft = (s, t, e) => t in s ? Ot(s, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : s[t] = e;
|
|
3
|
+
var h = (s, t, e) => Ft(s, typeof t != "symbol" ? t + "" : t, e);
|
|
4
|
+
import { Ghostty as Ht, Terminal as Qt, FitAddon as _t } from "ghostty-web";
|
|
5
|
+
import * as M from "@duckdb/duckdb-wasm";
|
|
6
|
+
const zt = "https://cdn.jsdelivr.net/npm/ghostty-web", qt = () => `${zt}@0.4.0/dist/ghostty-vt.wasm`;
|
|
7
|
+
class jt {
|
|
8
8
|
constructor() {
|
|
9
9
|
h(this, "terminal");
|
|
10
10
|
h(this, "fitAddon");
|
|
@@ -50,14 +50,14 @@ class ht {
|
|
|
50
50
|
* ```
|
|
51
51
|
*/
|
|
52
52
|
async init(t, e = {}) {
|
|
53
|
-
this.initialized || (this.container = t, this.options = e, this.currentTheme = e.theme ?? null, this.ghostty = await
|
|
53
|
+
this.initialized || (this.container = t, this.options = e, this.currentTheme = e.theme ?? null, this.ghostty = await Ht.load(qt()), this.createTerminal(), this.initialized = !0, this.focus());
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
56
|
* Creates or recreates the terminal instance.
|
|
57
57
|
* @internal
|
|
58
58
|
*/
|
|
59
59
|
createTerminal() {
|
|
60
|
-
this.container && (this.terminal = new
|
|
60
|
+
this.container && (this.terminal = new Qt({
|
|
61
61
|
ghostty: this.ghostty,
|
|
62
62
|
cursorBlink: !0,
|
|
63
63
|
fontSize: this.options.fontSize ?? 14,
|
|
@@ -65,13 +65,52 @@ class ht {
|
|
|
65
65
|
theme: this.currentTheme ? this.themeToGhostty(this.currentTheme) : void 0,
|
|
66
66
|
// Scrollback is in bytes, not lines. 10MB is a reasonable default.
|
|
67
67
|
scrollback: this.options.scrollback ?? 10 * 1024 * 1024
|
|
68
|
-
}), this.fitAddon = new
|
|
68
|
+
}), this.fitAddon = new _t(), this.terminal.loadAddon(this.fitAddon), this.container.innerHTML = "", this.terminal.open(this.container), this.fit(), window.addEventListener("resize", this.handleResize), typeof ResizeObserver < "u" && (this.resizeObserver?.disconnect(), this.resizeObserver = new ResizeObserver(() => {
|
|
69
69
|
this.fit();
|
|
70
70
|
}), this.resizeObserver.observe(this.container)), this.terminal.onData((t) => {
|
|
71
71
|
this.dataHandler?.(t);
|
|
72
72
|
}), this.terminal.onResize(({ cols: t, rows: e }) => {
|
|
73
73
|
this.resizeHandler?.(t, e);
|
|
74
|
-
}), this.setupMobileInput());
|
|
74
|
+
}), this.setupSafariClipboardWorkaround(), this.setupMobileInput());
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Sets up a workaround for Safari's clipboard restrictions.
|
|
78
|
+
*
|
|
79
|
+
* Safari requires clipboard operations to happen synchronously within a user gesture.
|
|
80
|
+
* The async Clipboard API loses the gesture context after an await, causing copy to fail.
|
|
81
|
+
* This workaround intercepts Cmd+C and uses the synchronous execCommand method.
|
|
82
|
+
*
|
|
83
|
+
* @internal
|
|
84
|
+
*/
|
|
85
|
+
setupSafariClipboardWorkaround() {
|
|
86
|
+
this.terminal && this.terminal.attachCustomKeyEventHandler?.((t) => {
|
|
87
|
+
if (t.metaKey && t.code === "KeyC" && t.type === "keydown") {
|
|
88
|
+
const e = this.terminal.getSelection?.();
|
|
89
|
+
if (e && e.length > 0)
|
|
90
|
+
return this.copyToClipboardSync(e), !0;
|
|
91
|
+
}
|
|
92
|
+
return !1;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Copies text to clipboard using synchronous methods that work in Safari.
|
|
97
|
+
*
|
|
98
|
+
* @internal
|
|
99
|
+
* @param text - The text to copy to clipboard
|
|
100
|
+
*/
|
|
101
|
+
copyToClipboardSync(t) {
|
|
102
|
+
const e = document.createElement("textarea");
|
|
103
|
+
e.value = t, e.style.position = "fixed", e.style.left = "-9999px", e.style.top = "0", e.style.opacity = "0", document.body.appendChild(e);
|
|
104
|
+
const i = document.activeElement;
|
|
105
|
+
try {
|
|
106
|
+
if (e.focus(), e.select(), e.setSelectionRange(0, t.length), !document.execCommand("copy") && navigator.clipboard && typeof ClipboardItem < "u") {
|
|
107
|
+
const n = new Blob([t], { type: "text/plain" }), o = new ClipboardItem({ "text/plain": n });
|
|
108
|
+
navigator.clipboard.write([o]).catch(() => {
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
} finally {
|
|
112
|
+
document.body.removeChild(e), i && i.focus();
|
|
113
|
+
}
|
|
75
114
|
}
|
|
76
115
|
/**
|
|
77
116
|
* Sets up mobile-specific functionality: touch scrolling and keyboard input.
|
|
@@ -339,7 +378,7 @@ class ht {
|
|
|
339
378
|
window.removeEventListener("resize", this.handleResize), this.resizeObserver?.disconnect(), this.resizeObserver = null, this.mobileInput?.remove(), this.mobileInput = null, this.terminal?.dispose(), this.initialized = !1, this.container = null;
|
|
340
379
|
}
|
|
341
380
|
}
|
|
342
|
-
class
|
|
381
|
+
class Wt {
|
|
343
382
|
constructor(t = {}) {
|
|
344
383
|
h(this, "db", null);
|
|
345
384
|
h(this, "conn", null);
|
|
@@ -377,20 +416,20 @@ class ct {
|
|
|
377
416
|
async init() {
|
|
378
417
|
if (this.initialized)
|
|
379
418
|
return;
|
|
380
|
-
const t =
|
|
419
|
+
const t = M.getJsDelivrBundles(), e = await M.selectBundle(t), i = URL.createObjectURL(
|
|
381
420
|
new Blob([`importScripts("${e.mainWorker}");`], {
|
|
382
421
|
type: "text/javascript"
|
|
383
422
|
})
|
|
384
423
|
);
|
|
385
424
|
this.worker = new Worker(i);
|
|
386
|
-
const r = new
|
|
387
|
-
this.db = new
|
|
425
|
+
const r = new M.VoidLogger();
|
|
426
|
+
this.db = new M.AsyncDuckDB(r, this.worker), await this.db.instantiate(e.mainModule, e.pthreadWorker), URL.revokeObjectURL(i);
|
|
388
427
|
const n = {
|
|
389
428
|
castDecimalToDouble: !0
|
|
390
429
|
};
|
|
391
430
|
this.options.storage === "opfs" && this.options.databasePath ? await this.db.open({
|
|
392
431
|
path: this.options.databasePath,
|
|
393
|
-
accessMode:
|
|
432
|
+
accessMode: M.DuckDBAccessMode.READ_WRITE,
|
|
394
433
|
query: n
|
|
395
434
|
}) : await this.db.open({
|
|
396
435
|
path: ":memory:",
|
|
@@ -422,20 +461,21 @@ class ct {
|
|
|
422
461
|
async executeQuery(t) {
|
|
423
462
|
if (!this.conn)
|
|
424
463
|
throw new Error("Database not initialized");
|
|
425
|
-
const e = performance.now(), i = await this.conn.query(t), r = performance.now() - e, n = i.schema.fields.map((
|
|
426
|
-
for (let
|
|
427
|
-
const
|
|
428
|
-
for (let
|
|
429
|
-
const
|
|
430
|
-
|
|
464
|
+
const e = performance.now(), i = await this.conn.query(t), r = performance.now() - e, n = i.schema.fields.map((c) => c.name), o = i.schema.fields.map((c) => String(c.type)), a = [];
|
|
465
|
+
for (let c = 0; c < i.numRows; c++) {
|
|
466
|
+
const f = [];
|
|
467
|
+
for (let u = 0; u < n.length; u++) {
|
|
468
|
+
const p = i.getChildAt(u);
|
|
469
|
+
f.push(p?.get(c));
|
|
431
470
|
}
|
|
432
|
-
|
|
471
|
+
a.push(f);
|
|
433
472
|
}
|
|
434
|
-
const
|
|
473
|
+
const l = typeof i.numRows == "bigint" ? Number(i.numRows) : i.numRows;
|
|
435
474
|
return {
|
|
436
475
|
columns: n,
|
|
437
|
-
|
|
438
|
-
|
|
476
|
+
columnTypes: o,
|
|
477
|
+
rows: a,
|
|
478
|
+
rowCount: l,
|
|
439
479
|
duration: r
|
|
440
480
|
};
|
|
441
481
|
}
|
|
@@ -531,8 +571,8 @@ class ct {
|
|
|
531
571
|
"SELECT table_name FROM information_schema.tables WHERE table_schema = 'main'"
|
|
532
572
|
);
|
|
533
573
|
for (const f of c.rows) {
|
|
534
|
-
const
|
|
535
|
-
|
|
574
|
+
const u = String(f[0]);
|
|
575
|
+
u.toLowerCase().startsWith(n) && o.push({ value: u, type: "table" });
|
|
536
576
|
}
|
|
537
577
|
} catch {
|
|
538
578
|
}
|
|
@@ -571,8 +611,8 @@ class ct {
|
|
|
571
611
|
for (const c of l)
|
|
572
612
|
c.toLowerCase().startsWith(n) && o.push({ value: c, type: "function" });
|
|
573
613
|
return o.sort((c, f) => {
|
|
574
|
-
const
|
|
575
|
-
return
|
|
614
|
+
const u = c.value.toLowerCase() === n, p = f.value.toLowerCase() === n;
|
|
615
|
+
return u && !p ? -1 : !u && p ? 1 : c.value.localeCompare(f.value);
|
|
576
616
|
}), o.slice(0, 20);
|
|
577
617
|
} catch {
|
|
578
618
|
return [];
|
|
@@ -611,8 +651,8 @@ class ct {
|
|
|
611
651
|
return (await this.executeQuery(
|
|
612
652
|
"SELECT table_catalog, table_schema, table_name FROM information_schema.tables ORDER BY table_catalog, table_schema, table_name"
|
|
613
653
|
)).rows.map((o) => {
|
|
614
|
-
const a = String(o[0]), l = String(o[1]), c = String(o[2]), f = r && a !== i,
|
|
615
|
-
return f &&
|
|
654
|
+
const a = String(o[0]), l = String(o[1]), c = String(o[2]), f = r && a !== i, u = l !== "main";
|
|
655
|
+
return f && u ? `${a}.${l}.${c}` : f ? `${a}.${c}` : u ? `${l}.${c}` : c;
|
|
616
656
|
});
|
|
617
657
|
} catch {
|
|
618
658
|
return [];
|
|
@@ -639,17 +679,64 @@ class ct {
|
|
|
639
679
|
if (!this.conn)
|
|
640
680
|
return [];
|
|
641
681
|
try {
|
|
642
|
-
const e = t.
|
|
682
|
+
const e = t.split(".");
|
|
683
|
+
let i;
|
|
684
|
+
if (e.length === 3) {
|
|
685
|
+
const n = e[0].replace(/'/g, "''"), o = e[1].replace(/'/g, "''"), a = e[2].replace(/'/g, "''");
|
|
686
|
+
i = `table_catalog = '${n}' AND table_schema = '${o}' AND table_name = '${a}'`;
|
|
687
|
+
} else if (e.length === 2) {
|
|
688
|
+
const n = e[0].replace(/'/g, "''"), o = e[1].replace(/'/g, "''");
|
|
689
|
+
i = `((table_catalog = '${n}' AND table_name = '${o}') OR (table_schema = '${n}' AND table_name = '${o}'))`;
|
|
690
|
+
} else
|
|
691
|
+
i = `table_name = '${t.replace(/'/g, "''")}'`;
|
|
643
692
|
return (await this.executeQuery(
|
|
644
|
-
`SELECT column_name, data_type FROM information_schema.columns WHERE
|
|
645
|
-
)).rows.map((
|
|
646
|
-
name: String(
|
|
647
|
-
type: String(
|
|
693
|
+
`SELECT column_name, data_type FROM information_schema.columns WHERE ${i} ORDER BY ordinal_position`
|
|
694
|
+
)).rows.map((n) => ({
|
|
695
|
+
name: String(n[0]),
|
|
696
|
+
type: String(n[1])
|
|
648
697
|
}));
|
|
649
698
|
} catch {
|
|
650
699
|
return [];
|
|
651
700
|
}
|
|
652
701
|
}
|
|
702
|
+
/**
|
|
703
|
+
* Gets CREATE TABLE statements for all tables and views in the database.
|
|
704
|
+
*
|
|
705
|
+
* This method queries the database catalog and generates DDL statements
|
|
706
|
+
* that describe the schema, useful for providing context to AI assistants.
|
|
707
|
+
*
|
|
708
|
+
* @returns A promise that resolves to a string containing all CREATE TABLE statements,
|
|
709
|
+
* or an empty string if no tables exist
|
|
710
|
+
*
|
|
711
|
+
* @example
|
|
712
|
+
* ```typescript
|
|
713
|
+
* const ddl = await db.getAllDDL();
|
|
714
|
+
* console.log(ddl);
|
|
715
|
+
* // CREATE TABLE users (id INTEGER, name VARCHAR, email VARCHAR);
|
|
716
|
+
* // CREATE TABLE orders (id INTEGER, user_id INTEGER, total DECIMAL);
|
|
717
|
+
* ```
|
|
718
|
+
*/
|
|
719
|
+
async getAllDDL() {
|
|
720
|
+
if (!this.conn)
|
|
721
|
+
return "";
|
|
722
|
+
try {
|
|
723
|
+
const t = await this.getTables();
|
|
724
|
+
if (t.length === 0)
|
|
725
|
+
return "";
|
|
726
|
+
const e = [];
|
|
727
|
+
for (const i of t) {
|
|
728
|
+
const r = await this.getTableSchema(i);
|
|
729
|
+
if (r.length > 0) {
|
|
730
|
+
const n = r.map((o) => `${o.name} ${o.type}`).join(", ");
|
|
731
|
+
e.push(`CREATE TABLE ${i} (${n});`);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return e.join(`
|
|
735
|
+
`);
|
|
736
|
+
} catch {
|
|
737
|
+
return "";
|
|
738
|
+
}
|
|
739
|
+
}
|
|
653
740
|
/**
|
|
654
741
|
* Registers a file in DuckDB's virtual filesystem.
|
|
655
742
|
*
|
|
@@ -773,35 +860,48 @@ class ct {
|
|
|
773
860
|
}
|
|
774
861
|
}
|
|
775
862
|
/**
|
|
776
|
-
*
|
|
863
|
+
* Validates SQL and returns error information if invalid.
|
|
777
864
|
*
|
|
778
|
-
* This
|
|
779
|
-
*
|
|
865
|
+
* This combines `is_valid_sql()` and `sql_error_message()` in a single query
|
|
866
|
+
* for efficiency. Returns validation result and error details in one call.
|
|
780
867
|
*
|
|
781
868
|
* @param sql - The SQL string to validate
|
|
782
|
-
* @returns A promise that resolves to
|
|
783
|
-
* or
|
|
869
|
+
* @returns A promise that resolves to an object with `isValid` boolean and
|
|
870
|
+
* optional `error` with details, or undefined if extension unavailable
|
|
784
871
|
*
|
|
785
872
|
* @example
|
|
786
873
|
* ```typescript
|
|
787
|
-
* const
|
|
788
|
-
*
|
|
789
|
-
*
|
|
790
|
-
*
|
|
791
|
-
* console.log(isInvalid); // false
|
|
874
|
+
* const result = await db.validateSQL('SELECT * FROM users WHERE');
|
|
875
|
+
* if (result && !result.isValid) {
|
|
876
|
+
* console.log(result.error?.exceptionMessage); // "syntax error at end of input"
|
|
877
|
+
* }
|
|
792
878
|
* ```
|
|
793
879
|
*/
|
|
794
|
-
async
|
|
795
|
-
if (!this.conn || !this.poachedLoaded)
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
880
|
+
async validateSQL(t) {
|
|
881
|
+
if (!(!this.conn || !this.poachedLoaded))
|
|
882
|
+
try {
|
|
883
|
+
const e = t.replace(/'/g, "''"), i = await this.executeQuery(
|
|
884
|
+
`SELECT is_valid_sql('${e}'), sql_error_message('${e}')::json`
|
|
885
|
+
), r = i.rows[0]?.[0] === !0, n = i.rows[0]?.[1];
|
|
886
|
+
if (r)
|
|
887
|
+
return { isValid: !0 };
|
|
888
|
+
if (n) {
|
|
889
|
+
const o = typeof n == "string" ? JSON.parse(n) : n;
|
|
890
|
+
if (o.exception_message || o.exception_type)
|
|
891
|
+
return {
|
|
892
|
+
isValid: !1,
|
|
893
|
+
error: {
|
|
894
|
+
exceptionType: o.exception_type || "",
|
|
895
|
+
exceptionMessage: o.exception_message || "",
|
|
896
|
+
position: o.position || "",
|
|
897
|
+
errorSubtype: o.error_subtype || ""
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
return { isValid: !1 };
|
|
902
|
+
} catch {
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
805
905
|
}
|
|
806
906
|
/**
|
|
807
907
|
* Closes the database connection and releases resources.
|
|
@@ -826,55 +926,55 @@ class ct {
|
|
|
826
926
|
this.conn && (await this.conn.close(), this.conn = null), this.db && (await this.db.terminate(), this.db = null), this.worker && (this.worker.terminate(), this.worker = null), this.initialized = !1;
|
|
827
927
|
}
|
|
828
928
|
}
|
|
829
|
-
const
|
|
830
|
-
function
|
|
929
|
+
const V = "\x1B[0m", Gt = "\x1B[1m", Vt = "\x1B[2m", Yt = "\x1B[30m", w = "\x1B[31m", b = "\x1B[32m", F = "\x1B[33m", tt = "\x1B[34m", Ct = "\x1B[35m", v = "\x1B[36m", et = "\x1B[37m", T = "\x1B[90m", Kt = "\x1B[91m", Xt = "\x1B[92m", Jt = "\x1B[93m", Zt = "\x1B[94m", te = "\x1B[95m", ee = "\x1B[96m", ie = "\x1B[97m", C = "\x1B[K";
|
|
930
|
+
function D(s) {
|
|
831
931
|
return `\x1B[${s}A`;
|
|
832
932
|
}
|
|
833
|
-
function
|
|
933
|
+
function I(s) {
|
|
834
934
|
return `\x1B[${s}B`;
|
|
835
935
|
}
|
|
836
|
-
function
|
|
936
|
+
function E(s) {
|
|
837
937
|
return `\x1B[${s}G`;
|
|
838
938
|
}
|
|
839
|
-
const
|
|
840
|
-
black:
|
|
841
|
-
red:
|
|
842
|
-
green:
|
|
843
|
-
yellow:
|
|
844
|
-
blue:
|
|
845
|
-
magenta:
|
|
846
|
-
cyan:
|
|
847
|
-
white:
|
|
939
|
+
const se = "\x1B[?25l", re = "\x1B[?25h", ne = {
|
|
940
|
+
black: Yt,
|
|
941
|
+
red: w,
|
|
942
|
+
green: b,
|
|
943
|
+
yellow: F,
|
|
944
|
+
blue: tt,
|
|
945
|
+
magenta: Ct,
|
|
946
|
+
cyan: v,
|
|
947
|
+
white: et,
|
|
848
948
|
brightBlack: T,
|
|
849
|
-
brightRed:
|
|
850
|
-
brightGreen:
|
|
851
|
-
brightYellow:
|
|
852
|
-
brightBlue:
|
|
853
|
-
brightMagenta:
|
|
854
|
-
brightCyan:
|
|
855
|
-
brightWhite:
|
|
949
|
+
brightRed: Kt,
|
|
950
|
+
brightGreen: Xt,
|
|
951
|
+
brightYellow: Jt,
|
|
952
|
+
brightBlue: Zt,
|
|
953
|
+
brightMagenta: te,
|
|
954
|
+
brightCyan: ee,
|
|
955
|
+
brightWhite: ie
|
|
856
956
|
};
|
|
857
|
-
function
|
|
858
|
-
return `${
|
|
957
|
+
function d(s, t) {
|
|
958
|
+
return `${ne[t] || t}${s}${V}`;
|
|
859
959
|
}
|
|
860
|
-
function
|
|
861
|
-
return `${
|
|
960
|
+
function L(s) {
|
|
961
|
+
return `${Gt}${s}${V}`;
|
|
862
962
|
}
|
|
863
|
-
function
|
|
864
|
-
return `${
|
|
963
|
+
function m(s) {
|
|
964
|
+
return `${Vt}${s}${V}`;
|
|
865
965
|
}
|
|
866
|
-
function
|
|
966
|
+
function oe(s) {
|
|
867
967
|
return s.replace(/\x1b\[[0-9;]*[A-Za-z]|\x1b\][^\x07]*\x07|\x1b[^[\]]/g, "");
|
|
868
968
|
}
|
|
869
|
-
function
|
|
870
|
-
return
|
|
969
|
+
function ae(s) {
|
|
970
|
+
return oe(s).length;
|
|
871
971
|
}
|
|
872
|
-
const
|
|
873
|
-
class
|
|
972
|
+
const le = 64 * 1024;
|
|
973
|
+
class ce {
|
|
874
974
|
constructor() {
|
|
875
975
|
h(this, "buffer", "");
|
|
876
976
|
h(this, "cursorPos", 0);
|
|
877
|
-
h(this, "maxSize",
|
|
977
|
+
h(this, "maxSize", le);
|
|
878
978
|
h(this, "terminalWidth", 80);
|
|
879
979
|
h(this, "promptLength", 0);
|
|
880
980
|
}
|
|
@@ -960,7 +1060,7 @@ class Lt {
|
|
|
960
1060
|
moveCursor(t, e) {
|
|
961
1061
|
let i = "";
|
|
962
1062
|
const r = e.row - t.row;
|
|
963
|
-
return r > 0 ? i +=
|
|
1063
|
+
return r > 0 ? i += I(r) : r < 0 && (i += D(-r)), (e.col !== t.col || r !== 0) && (i += E(e.col + 1)), i;
|
|
964
1064
|
}
|
|
965
1065
|
/**
|
|
966
1066
|
* Sets the maximum buffer size.
|
|
@@ -1052,9 +1152,9 @@ class Lt {
|
|
|
1052
1152
|
);
|
|
1053
1153
|
else {
|
|
1054
1154
|
if (a += r, e.row > o.row) {
|
|
1055
|
-
a +=
|
|
1155
|
+
a += C;
|
|
1056
1156
|
for (let c = o.row + 1; c <= e.row; c++)
|
|
1057
|
-
a +=
|
|
1157
|
+
a += I(1), a += E(1), a += C;
|
|
1058
1158
|
a += this.moveCursor(
|
|
1059
1159
|
{ row: e.row, col: 0 },
|
|
1060
1160
|
o
|
|
@@ -1079,9 +1179,9 @@ class Lt {
|
|
|
1079
1179
|
const n = this.getEndPosition();
|
|
1080
1180
|
let o = "";
|
|
1081
1181
|
if (o += r, e.row > n.row) {
|
|
1082
|
-
o +=
|
|
1182
|
+
o += C;
|
|
1083
1183
|
for (let a = n.row + 1; a <= e.row; a++)
|
|
1084
|
-
o +=
|
|
1184
|
+
o += I(1), o += E(1), o += C;
|
|
1085
1185
|
o += this.moveCursor({ row: e.row, col: 0 }, t);
|
|
1086
1186
|
} else
|
|
1087
1187
|
o += " ", o += this.moveCursor(
|
|
@@ -1153,10 +1253,10 @@ class Lt {
|
|
|
1153
1253
|
const t = this.getEndPosition(), e = this.getCursorPosition();
|
|
1154
1254
|
this.buffer = this.buffer.substring(0, this.cursorPos);
|
|
1155
1255
|
const i = this.getEndPosition();
|
|
1156
|
-
let r =
|
|
1256
|
+
let r = C;
|
|
1157
1257
|
if (t.row > i.row) {
|
|
1158
1258
|
for (let n = e.row + 1; n <= t.row; n++)
|
|
1159
|
-
r +=
|
|
1259
|
+
r += I(1), r += E(1), r += C;
|
|
1160
1260
|
r += this.moveCursor({ row: t.row, col: 0 }, e);
|
|
1161
1261
|
}
|
|
1162
1262
|
return r;
|
|
@@ -1169,9 +1269,9 @@ class Lt {
|
|
|
1169
1269
|
clearLine() {
|
|
1170
1270
|
const t = this.getEndPosition();
|
|
1171
1271
|
let e = this.moveToStart();
|
|
1172
|
-
if (e +=
|
|
1272
|
+
if (e += C, t.row > 0) {
|
|
1173
1273
|
for (let i = 1; i <= t.row; i++)
|
|
1174
|
-
e +=
|
|
1274
|
+
e += I(1), e += E(1), e += C;
|
|
1175
1275
|
e += this.moveCursor(
|
|
1176
1276
|
{ row: t.row, col: 0 },
|
|
1177
1277
|
{ row: 0, col: this.promptLength }
|
|
@@ -1200,13 +1300,13 @@ class Lt {
|
|
|
1200
1300
|
const e = this.getWordBeforeCursor(), i = this.cursorPos - e.length, r = this.buffer.substring(0, i), n = this.buffer.substring(this.cursorPos), o = this.getCursorPosition(), a = this.getEndPosition(), l = this.getPositionAt(i);
|
|
1201
1301
|
this.buffer = r + t + n, this.cursorPos = i + t.length;
|
|
1202
1302
|
const c = this.getCursorPosition(), f = this.getEndPosition();
|
|
1203
|
-
let
|
|
1204
|
-
if (
|
|
1205
|
-
for (let
|
|
1206
|
-
|
|
1207
|
-
|
|
1303
|
+
let u = "";
|
|
1304
|
+
if (u += this.moveCursor(o, l), u += C, a.row > l.row) {
|
|
1305
|
+
for (let p = l.row + 1; p <= a.row; p++)
|
|
1306
|
+
u += I(1), u += E(1), u += C;
|
|
1307
|
+
u += this.moveCursor({ row: a.row, col: 0 }, l);
|
|
1208
1308
|
}
|
|
1209
|
-
return
|
|
1309
|
+
return u += t + n, u += this.moveCursor(f, c), u;
|
|
1210
1310
|
}
|
|
1211
1311
|
/**
|
|
1212
1312
|
* Checks if the buffer is empty.
|
|
@@ -1225,8 +1325,8 @@ class Lt {
|
|
|
1225
1325
|
return this.buffer.trim().length === 0;
|
|
1226
1326
|
}
|
|
1227
1327
|
}
|
|
1228
|
-
const
|
|
1229
|
-
class
|
|
1328
|
+
const he = "duckdb-terminal", S = "history", B = 1e3;
|
|
1329
|
+
class ue {
|
|
1230
1330
|
constructor() {
|
|
1231
1331
|
h(this, "db", null);
|
|
1232
1332
|
h(this, "history", []);
|
|
@@ -1253,7 +1353,7 @@ class vt {
|
|
|
1253
1353
|
async init() {
|
|
1254
1354
|
if (!this.initialized)
|
|
1255
1355
|
try {
|
|
1256
|
-
this.db = await this.openDatabase(), this.history = await this.loadHistory(), this.history.length >
|
|
1356
|
+
this.db = await this.openDatabase(), this.history = await this.loadHistory(), this.history.length > B && (this.history = this.history.slice(-B), this.db && await this.trimHistory()), this.cursor = this.history.length, this.initialized = !0;
|
|
1257
1357
|
} catch (t) {
|
|
1258
1358
|
console.warn("IndexedDB not available, using in-memory history:", t), this.initialized = !0;
|
|
1259
1359
|
}
|
|
@@ -1263,10 +1363,10 @@ class vt {
|
|
|
1263
1363
|
*/
|
|
1264
1364
|
openDatabase() {
|
|
1265
1365
|
return new Promise((t, e) => {
|
|
1266
|
-
const i = indexedDB.open(
|
|
1366
|
+
const i = indexedDB.open(he, 1);
|
|
1267
1367
|
i.onerror = () => e(i.error), i.onsuccess = () => t(i.result), i.onupgradeneeded = (r) => {
|
|
1268
1368
|
const n = r.target.result;
|
|
1269
|
-
n.objectStoreNames.contains(
|
|
1369
|
+
n.objectStoreNames.contains(S) || n.createObjectStore(S, {
|
|
1270
1370
|
keyPath: "id",
|
|
1271
1371
|
autoIncrement: !0
|
|
1272
1372
|
});
|
|
@@ -1282,7 +1382,7 @@ class vt {
|
|
|
1282
1382
|
t([]);
|
|
1283
1383
|
return;
|
|
1284
1384
|
}
|
|
1285
|
-
const n = this.db.transaction(
|
|
1385
|
+
const n = this.db.transaction(S, "readonly").objectStore(S).getAll();
|
|
1286
1386
|
n.onerror = () => e(n.error), n.onsuccess = () => {
|
|
1287
1387
|
const o = n.result;
|
|
1288
1388
|
o.sort((a, l) => a.id - l.id), t(o.map((a) => a.command));
|
|
@@ -1312,7 +1412,7 @@ class vt {
|
|
|
1312
1412
|
this.reset();
|
|
1313
1413
|
return;
|
|
1314
1414
|
}
|
|
1315
|
-
for (this.history.push(e); this.history.length >
|
|
1415
|
+
for (this.history.push(e); this.history.length > B; )
|
|
1316
1416
|
this.history.shift();
|
|
1317
1417
|
if (this.db)
|
|
1318
1418
|
try {
|
|
@@ -1332,7 +1432,7 @@ class vt {
|
|
|
1332
1432
|
e();
|
|
1333
1433
|
return;
|
|
1334
1434
|
}
|
|
1335
|
-
const o = this.db.transaction(
|
|
1435
|
+
const o = this.db.transaction(S, "readwrite").objectStore(S).add({ command: t });
|
|
1336
1436
|
o.onerror = () => i(o.error), o.onsuccess = () => e();
|
|
1337
1437
|
});
|
|
1338
1438
|
}
|
|
@@ -1347,14 +1447,14 @@ class vt {
|
|
|
1347
1447
|
t();
|
|
1348
1448
|
return;
|
|
1349
1449
|
}
|
|
1350
|
-
const i = this.db.transaction(
|
|
1450
|
+
const i = this.db.transaction(S, "readwrite"), r = i.objectStore(S), n = r.count();
|
|
1351
1451
|
n.onsuccess = () => {
|
|
1352
1452
|
const o = n.result;
|
|
1353
|
-
if (o <=
|
|
1453
|
+
if (o <= B) {
|
|
1354
1454
|
t();
|
|
1355
1455
|
return;
|
|
1356
1456
|
}
|
|
1357
|
-
const a = o -
|
|
1457
|
+
const a = o - B, l = r.getAllKeys();
|
|
1358
1458
|
l.onsuccess = () => {
|
|
1359
1459
|
const f = l.result.slice(0, a);
|
|
1360
1460
|
if (f.length === 0) {
|
|
@@ -1362,8 +1462,8 @@ class vt {
|
|
|
1362
1462
|
return;
|
|
1363
1463
|
}
|
|
1364
1464
|
i.oncomplete = () => t(), i.onerror = () => e(i.error);
|
|
1365
|
-
for (const
|
|
1366
|
-
r.delete(
|
|
1465
|
+
for (const u of f)
|
|
1466
|
+
r.delete(u);
|
|
1367
1467
|
}, l.onerror = () => e(l.error);
|
|
1368
1468
|
}, n.onerror = () => e(n.error);
|
|
1369
1469
|
});
|
|
@@ -1429,13 +1529,13 @@ class vt {
|
|
|
1429
1529
|
async clear() {
|
|
1430
1530
|
if (this.history = [], this.cursor = 0, this.currentInput = "", this.db)
|
|
1431
1531
|
return new Promise((t, e) => {
|
|
1432
|
-
const n = this.db.transaction(
|
|
1532
|
+
const n = this.db.transaction(S, "readwrite").objectStore(S).clear();
|
|
1433
1533
|
n.onerror = () => e(n.error), n.onsuccess = () => t();
|
|
1434
1534
|
});
|
|
1435
1535
|
}
|
|
1436
1536
|
}
|
|
1437
|
-
const
|
|
1438
|
-
function
|
|
1537
|
+
const de = 50;
|
|
1538
|
+
function j(s) {
|
|
1439
1539
|
let t = 0;
|
|
1440
1540
|
for (const e of s) {
|
|
1441
1541
|
const i = e.charCodeAt(0);
|
|
@@ -1443,12 +1543,12 @@ function A(s) {
|
|
|
1443
1543
|
}
|
|
1444
1544
|
return t;
|
|
1445
1545
|
}
|
|
1446
|
-
function
|
|
1447
|
-
const e =
|
|
1546
|
+
function lt(s, t) {
|
|
1547
|
+
const e = j(s), i = t - e;
|
|
1448
1548
|
return i <= 0 ? s : s + " ".repeat(i);
|
|
1449
1549
|
}
|
|
1450
|
-
function
|
|
1451
|
-
if (
|
|
1550
|
+
function ct(s, t) {
|
|
1551
|
+
if (j(s) <= t) return s;
|
|
1452
1552
|
let e = "", i = 0;
|
|
1453
1553
|
for (const r of s) {
|
|
1454
1554
|
const n = r.charCodeAt(0) >= 4352 && r.charCodeAt(0) <= 65535 ? 2 : 1;
|
|
@@ -1457,10 +1557,10 @@ function Q(s, t) {
|
|
|
1457
1557
|
}
|
|
1458
1558
|
return e + "…";
|
|
1459
1559
|
}
|
|
1460
|
-
function
|
|
1560
|
+
function xt(s) {
|
|
1461
1561
|
return s >= Number.MIN_SAFE_INTEGER && s <= Number.MAX_SAFE_INTEGER ? Number(s) : s.toString();
|
|
1462
1562
|
}
|
|
1463
|
-
function
|
|
1563
|
+
function fe(s) {
|
|
1464
1564
|
const t = [];
|
|
1465
1565
|
if (s.months) {
|
|
1466
1566
|
const e = Math.floor(s.months / 12), i = s.months % 12;
|
|
@@ -1472,48 +1572,48 @@ function kt(s) {
|
|
|
1472
1572
|
}
|
|
1473
1573
|
return t.length ? t.join(" ") : "00:00:00";
|
|
1474
1574
|
}
|
|
1475
|
-
function
|
|
1575
|
+
function me(s) {
|
|
1476
1576
|
if (typeof s != "object" || s === null) return !1;
|
|
1477
1577
|
const t = s;
|
|
1478
1578
|
return "months" in t || "days" in t || "micros" in t;
|
|
1479
1579
|
}
|
|
1480
|
-
function
|
|
1481
|
-
return typeof t == "bigint" ?
|
|
1580
|
+
function vt(s, t) {
|
|
1581
|
+
return typeof t == "bigint" ? xt(t) : t instanceof Date ? t.toISOString() : t instanceof Map ? Object.fromEntries(t) : t instanceof Uint8Array ? "\\x" + Array.from(t).map((e) => e.toString(16).padStart(2, "0")).join("") : t;
|
|
1482
1582
|
}
|
|
1483
|
-
function
|
|
1484
|
-
return s == null ? t : typeof s == "string" ? s : typeof s == "boolean" ? s ? "true" : "false" : typeof s == "bigint" ? String(
|
|
1485
|
-
([i, r]) => `${
|
|
1486
|
-
).join(", ") + "}" : s instanceof Uint8Array ? "\\x" + Array.from(s).map((e) => e.toString(16).padStart(2, "0")).join("") : Array.isArray(s) ? "[" + s.map((e) =>
|
|
1583
|
+
function $(s, t) {
|
|
1584
|
+
return s == null ? t : typeof s == "string" ? s : typeof s == "boolean" ? s ? "true" : "false" : typeof s == "bigint" ? String(xt(s)) : s instanceof Date ? s.toISOString() : s instanceof Map ? "{" + Array.from(s.entries()).map(
|
|
1585
|
+
([i, r]) => `${$(i, t)}: ${$(r, t)}`
|
|
1586
|
+
).join(", ") + "}" : s instanceof Uint8Array ? "\\x" + Array.from(s).map((e) => e.toString(16).padStart(2, "0")).join("") : Array.isArray(s) ? "[" + s.map((e) => $(e, t)).join(", ") + "]" : me(s) ? fe(s) : typeof s == "object" ? JSON.stringify(s, vt) : String(s);
|
|
1487
1587
|
}
|
|
1488
|
-
function
|
|
1489
|
-
const { maxColumnWidth: i =
|
|
1588
|
+
function H(s, t, e = {}) {
|
|
1589
|
+
const { maxColumnWidth: i = de, nullValue: r = "NULL" } = e;
|
|
1490
1590
|
if (s.length === 0)
|
|
1491
1591
|
return "";
|
|
1492
1592
|
const n = t.map(
|
|
1493
1593
|
(l) => l.map((c) => {
|
|
1494
|
-
const f =
|
|
1495
|
-
return i ?
|
|
1594
|
+
const f = $(c, r);
|
|
1595
|
+
return i ? ct(f, i) : f;
|
|
1496
1596
|
})
|
|
1497
1597
|
), o = s.map((l, c) => {
|
|
1498
|
-
const f =
|
|
1499
|
-
const
|
|
1500
|
-
return Math.max(
|
|
1598
|
+
const f = j(l), u = n.reduce((p, g) => {
|
|
1599
|
+
const y = j(g[c] ?? "");
|
|
1600
|
+
return Math.max(p, y);
|
|
1501
1601
|
}, 0);
|
|
1502
|
-
return Math.min(Math.max(f,
|
|
1602
|
+
return Math.min(Math.max(f, u), i);
|
|
1503
1603
|
}), a = [];
|
|
1504
1604
|
a.push("┌" + o.map((l) => "─".repeat(l + 2)).join("┬") + "┐"), a.push(
|
|
1505
|
-
"│" + s.map((l, c) => " " +
|
|
1605
|
+
"│" + s.map((l, c) => " " + lt(ct(l, o[c]), o[c]) + " ").join("│") + "│"
|
|
1506
1606
|
), a.push("├" + o.map((l) => "─".repeat(l + 2)).join("┼") + "┤");
|
|
1507
1607
|
for (const l of n)
|
|
1508
1608
|
a.push(
|
|
1509
|
-
"│" + l.map((c, f) => " " +
|
|
1609
|
+
"│" + l.map((c, f) => " " + lt(c, o[f]) + " ").join("│") + "│"
|
|
1510
1610
|
);
|
|
1511
1611
|
return a.push("└" + o.map((l) => "─".repeat(l + 2)).join("┴") + "┘"), a.join(`
|
|
1512
1612
|
`);
|
|
1513
1613
|
}
|
|
1514
|
-
function
|
|
1614
|
+
function Q(s, t) {
|
|
1515
1615
|
const e = (r) => {
|
|
1516
|
-
const n =
|
|
1616
|
+
const n = $(r, "");
|
|
1517
1617
|
return n.includes(",") || n.includes('"') || n.includes(`
|
|
1518
1618
|
`) ? '"' + n.replace(/"/g, '""') + '"' : n;
|
|
1519
1619
|
}, i = [];
|
|
@@ -1523,36 +1623,36 @@ function N(s, t) {
|
|
|
1523
1623
|
return i.join(`
|
|
1524
1624
|
`);
|
|
1525
1625
|
}
|
|
1526
|
-
function
|
|
1527
|
-
const e = (r) =>
|
|
1626
|
+
function _(s, t) {
|
|
1627
|
+
const e = (r) => $(r, "").replace(/\t/g, " ").replace(/\n/g, " "), i = [];
|
|
1528
1628
|
i.push(s.map(e).join(" "));
|
|
1529
1629
|
for (const r of t)
|
|
1530
1630
|
i.push(r.map(e).join(" "));
|
|
1531
1631
|
return i.join(`
|
|
1532
1632
|
`);
|
|
1533
1633
|
}
|
|
1534
|
-
function
|
|
1634
|
+
function z(s, t) {
|
|
1535
1635
|
const e = t.map((i) => {
|
|
1536
1636
|
const r = {};
|
|
1537
1637
|
return s.forEach((n, o) => {
|
|
1538
1638
|
r[n] = i[o] ?? null;
|
|
1539
1639
|
}), r;
|
|
1540
1640
|
});
|
|
1541
|
-
return JSON.stringify(e,
|
|
1641
|
+
return JSON.stringify(e, vt, 2);
|
|
1542
1642
|
}
|
|
1543
|
-
const
|
|
1544
|
-
class
|
|
1545
|
-
constructor(t, e, i =
|
|
1643
|
+
const it = 100 * 1024 * 1024;
|
|
1644
|
+
class pe extends Error {
|
|
1645
|
+
constructor(t, e, i = it) {
|
|
1546
1646
|
super(
|
|
1547
|
-
`File "${t}" (${
|
|
1647
|
+
`File "${t}" (${U(e)}) exceeds maximum size of ${U(i)}`
|
|
1548
1648
|
), this.filename = t, this.size = e, this.maxSize = i, this.name = "FileSizeError";
|
|
1549
1649
|
}
|
|
1550
1650
|
}
|
|
1551
|
-
function
|
|
1651
|
+
function ge(s, t = it) {
|
|
1552
1652
|
if (s.size > t)
|
|
1553
|
-
throw new
|
|
1653
|
+
throw new pe(s.name, s.size, t);
|
|
1554
1654
|
}
|
|
1555
|
-
async function
|
|
1655
|
+
async function Tt(s) {
|
|
1556
1656
|
return new Promise((t) => {
|
|
1557
1657
|
const e = document.createElement("input");
|
|
1558
1658
|
e.type = "file", e.multiple = s?.multiple ?? !0, e.accept = s?.accept ?? ".csv,.parquet,.json,.db,.duckdb", e.onchange = () => {
|
|
@@ -1563,32 +1663,36 @@ async function V(s) {
|
|
|
1563
1663
|
}, e.click();
|
|
1564
1664
|
});
|
|
1565
1665
|
}
|
|
1566
|
-
async function
|
|
1567
|
-
return
|
|
1666
|
+
async function we(s, t = it) {
|
|
1667
|
+
return ge(s, t), new Promise((e, i) => {
|
|
1568
1668
|
const r = new FileReader();
|
|
1569
1669
|
r.onload = () => {
|
|
1570
1670
|
e(new Uint8Array(r.result));
|
|
1571
1671
|
}, r.onerror = () => i(r.error), r.readAsArrayBuffer(s);
|
|
1572
1672
|
});
|
|
1573
1673
|
}
|
|
1574
|
-
function
|
|
1674
|
+
function U(s) {
|
|
1575
1675
|
if (s === 0) return "0 B";
|
|
1576
1676
|
const t = 1024, e = ["B", "KB", "MB", "GB"], i = Math.floor(Math.log(s) / Math.log(t));
|
|
1577
1677
|
return parseFloat((s / Math.pow(t, i)).toFixed(2)) + " " + e[i];
|
|
1578
1678
|
}
|
|
1579
|
-
function
|
|
1679
|
+
function Et(s) {
|
|
1580
1680
|
const t = s.lastIndexOf(".");
|
|
1581
1681
|
return t === -1 ? "" : s.substring(t + 1).toLowerCase();
|
|
1582
1682
|
}
|
|
1583
|
-
function
|
|
1683
|
+
function ye(s) {
|
|
1584
1684
|
return {
|
|
1585
1685
|
name: s.name,
|
|
1586
1686
|
size: s.size,
|
|
1587
|
-
type: s.type ||
|
|
1687
|
+
type: s.type || Et(s.name),
|
|
1588
1688
|
lastModified: new Date(s.lastModified)
|
|
1589
1689
|
};
|
|
1590
1690
|
}
|
|
1591
|
-
function
|
|
1691
|
+
function be(s, t, e = "application/octet-stream") {
|
|
1692
|
+
const i = s instanceof Uint8Array ? [new Uint8Array(s)] : [s], r = new Blob(i, { type: e }), n = URL.createObjectURL(r), o = document.createElement("a");
|
|
1693
|
+
o.href = n, o.download = t, o.click(), URL.revokeObjectURL(n);
|
|
1694
|
+
}
|
|
1695
|
+
function Ce(s, t) {
|
|
1592
1696
|
const e = (n) => {
|
|
1593
1697
|
n.preventDefault(), n.stopPropagation(), s.classList.add("drag-over");
|
|
1594
1698
|
}, i = (n) => {
|
|
@@ -1602,7 +1706,7 @@ function Nt(s, t) {
|
|
|
1602
1706
|
s.removeEventListener("dragover", e), s.removeEventListener("dragleave", i), s.removeEventListener("drop", r);
|
|
1603
1707
|
};
|
|
1604
1708
|
}
|
|
1605
|
-
async function
|
|
1709
|
+
async function st(s) {
|
|
1606
1710
|
try {
|
|
1607
1711
|
if (navigator.clipboard && navigator.clipboard.writeText)
|
|
1608
1712
|
return await navigator.clipboard.writeText(s), !0;
|
|
@@ -1617,50 +1721,50 @@ async function X(s) {
|
|
|
1617
1721
|
return !1;
|
|
1618
1722
|
}
|
|
1619
1723
|
}
|
|
1620
|
-
async function
|
|
1724
|
+
async function xe() {
|
|
1621
1725
|
try {
|
|
1622
1726
|
return navigator.clipboard && navigator.clipboard.readText ? await navigator.clipboard.readText() : null;
|
|
1623
1727
|
} catch {
|
|
1624
1728
|
return null;
|
|
1625
1729
|
}
|
|
1626
1730
|
}
|
|
1627
|
-
function
|
|
1731
|
+
function zi() {
|
|
1628
1732
|
return !!(navigator.clipboard && navigator.clipboard.writeText);
|
|
1629
1733
|
}
|
|
1630
|
-
function
|
|
1734
|
+
function ve(s) {
|
|
1631
1735
|
switch (s) {
|
|
1632
1736
|
case "KEYWORD":
|
|
1633
|
-
return
|
|
1737
|
+
return tt;
|
|
1634
1738
|
case "IDENTIFIER":
|
|
1635
1739
|
return "";
|
|
1636
1740
|
// No color for identifiers
|
|
1637
1741
|
case "OPERATOR":
|
|
1638
|
-
return
|
|
1742
|
+
return et;
|
|
1639
1743
|
case "NUMERIC_CONSTANT":
|
|
1640
|
-
return
|
|
1744
|
+
return Ct;
|
|
1641
1745
|
case "STRING_CONSTANT":
|
|
1642
|
-
return
|
|
1746
|
+
return b;
|
|
1643
1747
|
case "COMMENT":
|
|
1644
1748
|
return T;
|
|
1645
1749
|
case "ERROR":
|
|
1646
|
-
return
|
|
1750
|
+
return w;
|
|
1647
1751
|
default:
|
|
1648
1752
|
return "";
|
|
1649
1753
|
}
|
|
1650
1754
|
}
|
|
1651
|
-
function
|
|
1755
|
+
function K(s, t) {
|
|
1652
1756
|
if (t.length === 0)
|
|
1653
1757
|
return s;
|
|
1654
1758
|
let e = "", i = 0;
|
|
1655
1759
|
for (let r = 0; r < t.length; r++) {
|
|
1656
1760
|
const n = t[r], o = t[r + 1], a = n.position, l = o ? o.position : s.length;
|
|
1657
1761
|
a > i && (e += s.slice(i, a));
|
|
1658
|
-
const c = s.slice(a, l), f =
|
|
1659
|
-
f ? e += f + c +
|
|
1762
|
+
const c = s.slice(a, l), f = ve(n.category);
|
|
1763
|
+
f ? e += f + c + V : e += c, i = l;
|
|
1660
1764
|
}
|
|
1661
1765
|
return i < s.length && (e += s.slice(i)), e;
|
|
1662
1766
|
}
|
|
1663
|
-
function
|
|
1767
|
+
function Te(s) {
|
|
1664
1768
|
const t = s.trim();
|
|
1665
1769
|
if (!t) return !1;
|
|
1666
1770
|
let e = !1, i = !1, r = !1, n = !1, o = "";
|
|
@@ -1703,7 +1807,7 @@ function ye(s) {
|
|
|
1703
1807
|
}
|
|
1704
1808
|
return o === ";";
|
|
1705
1809
|
}
|
|
1706
|
-
function
|
|
1810
|
+
function Ee(s, t) {
|
|
1707
1811
|
let e = null;
|
|
1708
1812
|
const i = function(...r) {
|
|
1709
1813
|
e !== null && clearTimeout(e), e = setTimeout(() => {
|
|
@@ -1714,7 +1818,7 @@ function Ot(s, t) {
|
|
|
1714
1818
|
e !== null && (clearTimeout(e), e = null);
|
|
1715
1819
|
}, i;
|
|
1716
1820
|
}
|
|
1717
|
-
function
|
|
1821
|
+
function Se(s) {
|
|
1718
1822
|
const t = [];
|
|
1719
1823
|
let e = "", i = null, r = !1, n = !1;
|
|
1720
1824
|
for (let o = 0; o < s.length; o++) {
|
|
@@ -1769,31 +1873,31 @@ function _t(s) {
|
|
|
1769
1873
|
}
|
|
1770
1874
|
return (e || n) && t.push(e), t;
|
|
1771
1875
|
}
|
|
1772
|
-
function
|
|
1773
|
-
const t =
|
|
1876
|
+
function Le(s) {
|
|
1877
|
+
const t = Se(s), e = t[0]?.toLowerCase() ?? "", i = t.slice(1);
|
|
1774
1878
|
return { command: e, args: i, allArgs: t };
|
|
1775
1879
|
}
|
|
1776
|
-
const
|
|
1777
|
-
function
|
|
1778
|
-
return
|
|
1880
|
+
const k = /https?:\/\/[^\s<>"\])\[}{'|^`\\]+/gi, Pe = "\x1B]8;;", ke = "\x07", Ie = "\x1B]8;;\x07";
|
|
1881
|
+
function qi(s) {
|
|
1882
|
+
return k.lastIndex = 0, k.test(s);
|
|
1779
1883
|
}
|
|
1780
|
-
function
|
|
1781
|
-
return
|
|
1884
|
+
function ji(s) {
|
|
1885
|
+
return k.lastIndex = 0, s.match(k) || [];
|
|
1782
1886
|
}
|
|
1783
|
-
function
|
|
1784
|
-
return `${
|
|
1887
|
+
function St(s, t) {
|
|
1888
|
+
return `${Pe}${s}${ke}${t ?? s}${Ie}`;
|
|
1785
1889
|
}
|
|
1786
|
-
function
|
|
1787
|
-
return
|
|
1890
|
+
function $e(s) {
|
|
1891
|
+
return k.lastIndex = 0, s.replace(k, (t) => St(t));
|
|
1788
1892
|
}
|
|
1789
|
-
function
|
|
1893
|
+
function Wi(s) {
|
|
1790
1894
|
try {
|
|
1791
1895
|
return new URL(s), !0;
|
|
1792
1896
|
} catch {
|
|
1793
1897
|
return !1;
|
|
1794
1898
|
}
|
|
1795
1899
|
}
|
|
1796
|
-
function
|
|
1900
|
+
function Ae(s, t = 60) {
|
|
1797
1901
|
if (s.length <= t)
|
|
1798
1902
|
return s;
|
|
1799
1903
|
try {
|
|
@@ -1806,7 +1910,7 @@ function Gt(s, t = 60) {
|
|
|
1806
1910
|
return s.substring(0, t - 3) + "...";
|
|
1807
1911
|
}
|
|
1808
1912
|
}
|
|
1809
|
-
class
|
|
1913
|
+
class Re {
|
|
1810
1914
|
constructor() {
|
|
1811
1915
|
h(this, "enabled", !0);
|
|
1812
1916
|
h(this, "maxURLLength", 80);
|
|
@@ -1833,252 +1937,2419 @@ class qt {
|
|
|
1833
1937
|
* Process text and add hyperlinks to URLs
|
|
1834
1938
|
*/
|
|
1835
1939
|
process(t) {
|
|
1836
|
-
return this.enabled ?
|
|
1940
|
+
return this.enabled ? $e(t) : t;
|
|
1837
1941
|
}
|
|
1838
1942
|
/**
|
|
1839
1943
|
* Process text with truncated URL display
|
|
1840
1944
|
*/
|
|
1841
1945
|
processWithTruncation(t) {
|
|
1842
|
-
return this.enabled ? (
|
|
1843
|
-
const i =
|
|
1844
|
-
return
|
|
1946
|
+
return this.enabled ? (k.lastIndex = 0, t.replace(k, (e) => {
|
|
1947
|
+
const i = Ae(e, this.maxURLLength);
|
|
1948
|
+
return St(e, i);
|
|
1845
1949
|
})) : t;
|
|
1846
1950
|
}
|
|
1847
1951
|
}
|
|
1848
|
-
const
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
brightGreen: "#23d18b",
|
|
1864
|
-
brightYellow: "#f5f543",
|
|
1865
|
-
brightBlue: "#3b8eea",
|
|
1866
|
-
brightMagenta: "#d670d6",
|
|
1867
|
-
brightCyan: "#29b8db",
|
|
1868
|
-
brightWhite: "#ffffff"
|
|
1869
|
-
}, Vt = {
|
|
1870
|
-
background: "#ffffff",
|
|
1871
|
-
foreground: "#1e1e1e",
|
|
1872
|
-
cursor: "#1e1e1e",
|
|
1873
|
-
selection: "#add6ff",
|
|
1874
|
-
black: "#000000",
|
|
1875
|
-
red: "#cd3131",
|
|
1876
|
-
green: "#008000",
|
|
1877
|
-
yellow: "#795e00",
|
|
1878
|
-
blue: "#0451a5",
|
|
1879
|
-
magenta: "#bc05bc",
|
|
1880
|
-
cyan: "#0598bc",
|
|
1881
|
-
white: "#e5e5e5",
|
|
1882
|
-
brightBlack: "#666666",
|
|
1883
|
-
brightRed: "#cd3131",
|
|
1884
|
-
brightGreen: "#14ce14",
|
|
1885
|
-
brightYellow: "#b5ba00",
|
|
1886
|
-
brightBlue: "#0451a5",
|
|
1887
|
-
brightMagenta: "#bc05bc",
|
|
1888
|
-
brightCyan: "#0598bc",
|
|
1889
|
-
brightWhite: "#a5a5a5"
|
|
1890
|
-
}, Kt = {
|
|
1891
|
-
name: "dark",
|
|
1892
|
-
colors: Yt
|
|
1893
|
-
}, Xt = {
|
|
1894
|
-
name: "light",
|
|
1895
|
-
colors: Vt
|
|
1896
|
-
};
|
|
1897
|
-
function U(s) {
|
|
1898
|
-
return s === "light" ? Xt : Kt;
|
|
1899
|
-
}
|
|
1900
|
-
function Jt() {
|
|
1901
|
-
try {
|
|
1902
|
-
const s = localStorage.getItem("duckdb-terminal-theme");
|
|
1903
|
-
if (s === "light" || s === "dark")
|
|
1904
|
-
return s;
|
|
1905
|
-
} catch {
|
|
1952
|
+
const Ne = {
|
|
1953
|
+
widthPercent: 80,
|
|
1954
|
+
heightPercent: 70,
|
|
1955
|
+
padding: 20,
|
|
1956
|
+
minWidth: 400,
|
|
1957
|
+
minHeight: 300
|
|
1958
|
+
}, Lt = "https://cdn.jsdelivr.net/npm/uplot", Me = () => `${Lt}@1.6.32/dist/uPlot.iife.min.js`, Be = () => `${Lt}@1.6.32/dist/uPlot.min.css`;
|
|
1959
|
+
class De {
|
|
1960
|
+
constructor() {
|
|
1961
|
+
h(this, "state", "idle");
|
|
1962
|
+
h(this, "uplot", null);
|
|
1963
|
+
h(this, "loadPromise", null);
|
|
1964
|
+
h(this, "error", null);
|
|
1965
|
+
/** Timeout for CDN load in milliseconds */
|
|
1966
|
+
h(this, "LOAD_TIMEOUT", 1e4);
|
|
1906
1967
|
}
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
} catch {
|
|
1968
|
+
/**
|
|
1969
|
+
* Get current loading state
|
|
1970
|
+
*/
|
|
1971
|
+
getState() {
|
|
1972
|
+
return this.state;
|
|
1913
1973
|
}
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
h(this, "database");
|
|
1920
|
-
h(this, "inputBuffer");
|
|
1921
|
-
h(this, "history");
|
|
1922
|
-
h(this, "state", "idle");
|
|
1923
|
-
h(this, "collectedSQL", []);
|
|
1924
|
-
h(this, "commands", /* @__PURE__ */ new Map());
|
|
1925
|
-
h(this, "showTimer", !1);
|
|
1926
|
-
h(this, "outputMode", "table");
|
|
1927
|
-
h(this, "currentThemeName");
|
|
1928
|
-
h(this, "customTheme", null);
|
|
1929
|
-
h(this, "config");
|
|
1930
|
-
h(this, "loadedFiles", /* @__PURE__ */ new Map());
|
|
1931
|
-
h(this, "syntaxHighlighting", !0);
|
|
1932
|
-
h(this, "lastQueryResult", null);
|
|
1933
|
-
h(this, "linkProvider");
|
|
1934
|
-
// Event emitter
|
|
1935
|
-
h(this, "eventListeners", /* @__PURE__ */ new Map());
|
|
1936
|
-
// Pagination state (0 = disabled by default)
|
|
1937
|
-
h(this, "pageSize", 0);
|
|
1938
|
-
h(this, "paginationQuery", null);
|
|
1939
|
-
h(this, "currentPage", 0);
|
|
1940
|
-
h(this, "totalRows", 0);
|
|
1941
|
-
// Prompt customization
|
|
1942
|
-
h(this, "prompt");
|
|
1943
|
-
h(this, "continuationPrompt");
|
|
1944
|
-
// Debounced syntax highlighting (150ms delay to avoid excessive redraws)
|
|
1945
|
-
h(this, "debouncedHighlight", Ot(() => this.redrawLineHighlighted(), 150));
|
|
1946
|
-
// Query queue to prevent race conditions
|
|
1947
|
-
h(this, "queryQueue", Promise.resolve(null));
|
|
1948
|
-
// Cleanup function for drag-and-drop
|
|
1949
|
-
h(this, "dragDropCleanup", null);
|
|
1950
|
-
this.config = t, this.terminalAdapter = new ht(), this.database = new ct({
|
|
1951
|
-
storage: t.storage ?? "memory",
|
|
1952
|
-
databasePath: t.databasePath
|
|
1953
|
-
}), this.inputBuffer = new Lt(), this.history = new vt(), this.linkProvider = new qt(), t.linkDetection === !1 && this.linkProvider.setEnabled(!1), this.prompt = t.prompt ?? W, this.continuationPrompt = t.continuationPrompt ?? j, typeof t.theme == "object" ? (this.customTheme = t.theme, this.currentThemeName = "custom") : this.currentThemeName = t.theme ?? Jt(), this.registerCommands();
|
|
1974
|
+
/**
|
|
1975
|
+
* Check if uPlot is already loaded
|
|
1976
|
+
*/
|
|
1977
|
+
isLoaded() {
|
|
1978
|
+
return this.state === "loaded" && this.uplot !== null;
|
|
1954
1979
|
}
|
|
1955
|
-
// ==================== Event Emitter ====================
|
|
1956
1980
|
/**
|
|
1957
|
-
*
|
|
1958
|
-
*
|
|
1959
|
-
* The terminal emits various events during its lifecycle that you can
|
|
1960
|
-
* subscribe to for monitoring, logging, or integrating with your application.
|
|
1961
|
-
*
|
|
1962
|
-
* @typeParam K - The event type key from {@link TerminalEvents}
|
|
1963
|
-
* @param event - The event name to subscribe to
|
|
1964
|
-
* @param listener - The callback function to invoke when the event occurs
|
|
1965
|
-
* @returns An unsubscribe function that removes the listener when called
|
|
1966
|
-
*
|
|
1967
|
-
* @example Subscribe to query events
|
|
1968
|
-
* ```typescript
|
|
1969
|
-
* const unsubscribe = terminal.on('queryEnd', ({ sql, result, duration }) => {
|
|
1970
|
-
* console.log(`Query completed in ${duration}ms`);
|
|
1971
|
-
* });
|
|
1972
|
-
*
|
|
1973
|
-
* // Later, to stop listening:
|
|
1974
|
-
* unsubscribe();
|
|
1975
|
-
* ```
|
|
1976
|
-
*
|
|
1977
|
-
* @example Monitor state changes
|
|
1978
|
-
* ```typescript
|
|
1979
|
-
* terminal.on('stateChange', ({ state, previous }) => {
|
|
1980
|
-
* console.log(`Terminal state: ${previous} -> ${state}`);
|
|
1981
|
-
* });
|
|
1982
|
-
* ```
|
|
1981
|
+
* Get the loaded uPlot constructor, or null if not loaded
|
|
1983
1982
|
*/
|
|
1984
|
-
|
|
1985
|
-
return this.
|
|
1983
|
+
getUPlot() {
|
|
1984
|
+
return this.uplot;
|
|
1986
1985
|
}
|
|
1987
1986
|
/**
|
|
1988
|
-
*
|
|
1989
|
-
*
|
|
1990
|
-
* This is an alternative to using the unsubscribe function returned by {@link on}.
|
|
1991
|
-
*
|
|
1992
|
-
* @typeParam K - The event type key from {@link TerminalEvents}
|
|
1993
|
-
* @param event - The event name to unsubscribe from
|
|
1994
|
-
* @param listener - The callback function to remove
|
|
1995
|
-
*
|
|
1996
|
-
* @example
|
|
1997
|
-
* ```typescript
|
|
1998
|
-
* const handler = ({ sql }) => console.log(sql);
|
|
1999
|
-
* terminal.on('queryStart', handler);
|
|
2000
|
-
*
|
|
2001
|
-
* // Later:
|
|
2002
|
-
* terminal.off('queryStart', handler);
|
|
2003
|
-
* ```
|
|
1987
|
+
* Get the loading error if any
|
|
2004
1988
|
*/
|
|
2005
|
-
|
|
2006
|
-
this.
|
|
1989
|
+
getError() {
|
|
1990
|
+
return this.error;
|
|
2007
1991
|
}
|
|
2008
1992
|
/**
|
|
2009
|
-
*
|
|
2010
|
-
*
|
|
2011
|
-
*
|
|
2012
|
-
* @typeParam K - The event type key from {@link TerminalEvents}
|
|
2013
|
-
* @param event - The event name to emit
|
|
2014
|
-
* @param payload - The event payload to pass to listeners
|
|
1993
|
+
* Load uPlot library from CDN.
|
|
1994
|
+
* Returns cached instance if already loaded.
|
|
1995
|
+
* Prevents duplicate load attempts.
|
|
2015
1996
|
*/
|
|
2016
|
-
|
|
2017
|
-
this.
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
1997
|
+
async load() {
|
|
1998
|
+
if (this.state === "loaded" && this.uplot)
|
|
1999
|
+
return this.uplot;
|
|
2000
|
+
if (this.state === "loading" && this.loadPromise)
|
|
2001
|
+
return this.loadPromise;
|
|
2002
|
+
if (this.state === "error" && this.error)
|
|
2003
|
+
throw this.error;
|
|
2004
|
+
this.state = "loading", this.loadPromise = this.doLoad();
|
|
2005
|
+
try {
|
|
2006
|
+
return this.uplot = await this.loadPromise, this.state = "loaded", this.uplot;
|
|
2007
|
+
} catch (t) {
|
|
2008
|
+
throw this.state = "error", this.error = t instanceof Error ? t : new Error(String(t)), this.error;
|
|
2009
|
+
}
|
|
2024
2010
|
}
|
|
2025
2011
|
/**
|
|
2026
|
-
*
|
|
2027
|
-
*
|
|
2028
|
-
* @internal
|
|
2029
|
-
* @param newState - The new terminal state
|
|
2012
|
+
* Reset loader state (mainly for testing)
|
|
2030
2013
|
*/
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
e !== t && (this.state = t, this.emit("stateChange", { state: t, previous: e }));
|
|
2014
|
+
reset() {
|
|
2015
|
+
this.state = "idle", this.uplot = null, this.loadPromise = null, this.error = null;
|
|
2034
2016
|
}
|
|
2035
|
-
// ==================== Syntax Highlighting ====================
|
|
2036
2017
|
/**
|
|
2037
|
-
*
|
|
2038
|
-
*
|
|
2039
|
-
* This is used for history navigation and examples display where async
|
|
2040
|
-
* highlighting is not practical. Returns unhighlighted SQL since the
|
|
2041
|
-
* DuckDB tokenizer is async.
|
|
2042
|
-
*
|
|
2043
|
-
* @param sql - The SQL string to highlight
|
|
2044
|
-
* @returns The SQL (highlighting not applied in sync context)
|
|
2018
|
+
* Perform the actual loading of JS and CSS
|
|
2045
2019
|
*/
|
|
2046
|
-
|
|
2047
|
-
|
|
2020
|
+
async doLoad() {
|
|
2021
|
+
await Promise.all([this.loadCSS(), this.loadJS()]);
|
|
2022
|
+
const t = window.uPlot;
|
|
2023
|
+
if (!t)
|
|
2024
|
+
throw new Error("uPlot not found on window after loading");
|
|
2025
|
+
return t;
|
|
2048
2026
|
}
|
|
2049
2027
|
/**
|
|
2050
|
-
*
|
|
2051
|
-
*
|
|
2052
|
-
* This method clears the current line content and rewrites it with
|
|
2053
|
-
* color codes for SQL keywords, strings, numbers, etc. Called on
|
|
2054
|
-
* delimiter characters (space, semicolon, parentheses, comma) via debouncing.
|
|
2055
|
-
*
|
|
2056
|
-
* Uses DuckDB's internal parser via the poached extension for accurate tokenization.
|
|
2028
|
+
* Load uPlot JavaScript from CDN
|
|
2057
2029
|
*/
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2030
|
+
loadJS() {
|
|
2031
|
+
return new Promise((t, e) => {
|
|
2032
|
+
if (window.uPlot) {
|
|
2033
|
+
t();
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
2036
|
+
const i = document.createElement("script");
|
|
2037
|
+
i.src = Me(), i.async = !0;
|
|
2038
|
+
const r = setTimeout(() => {
|
|
2039
|
+
e(new Error("Timeout loading uPlot JavaScript"));
|
|
2040
|
+
}, this.LOAD_TIMEOUT);
|
|
2041
|
+
i.onload = () => {
|
|
2042
|
+
clearTimeout(r), t();
|
|
2043
|
+
}, i.onerror = () => {
|
|
2044
|
+
clearTimeout(r), e(new Error("Failed to load uPlot JavaScript from CDN"));
|
|
2045
|
+
}, document.head.appendChild(i);
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
/**
|
|
2049
|
+
* Load uPlot CSS from CDN
|
|
2050
|
+
*/
|
|
2051
|
+
loadCSS() {
|
|
2052
|
+
return new Promise((t, e) => {
|
|
2053
|
+
if (document.querySelector(
|
|
2054
|
+
'link[href*="uPlot.min.css"]'
|
|
2055
|
+
)) {
|
|
2056
|
+
t();
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
2059
|
+
const r = document.createElement("link");
|
|
2060
|
+
r.rel = "stylesheet", r.href = Be();
|
|
2061
|
+
const n = setTimeout(() => {
|
|
2062
|
+
e(new Error("Timeout loading uPlot CSS"));
|
|
2063
|
+
}, this.LOAD_TIMEOUT);
|
|
2064
|
+
r.onload = () => {
|
|
2065
|
+
clearTimeout(n), t();
|
|
2066
|
+
}, r.onerror = () => {
|
|
2067
|
+
clearTimeout(n), e(new Error("Failed to load uPlot CSS from CDN"));
|
|
2068
|
+
}, document.head.appendChild(r);
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
const Ue = new De();
|
|
2073
|
+
class Oe {
|
|
2074
|
+
/**
|
|
2075
|
+
* Creates a new ChartOverlay instance.
|
|
2076
|
+
* @param container - The parent HTML element to attach the overlay to
|
|
2077
|
+
* @param events - Event handlers for overlay interactions (dismiss, export)
|
|
2078
|
+
* @param dimensions - Configuration for overlay sizing and positioning
|
|
2079
|
+
*/
|
|
2080
|
+
constructor(t, e = {}, i = Ne) {
|
|
2081
|
+
h(this, "container");
|
|
2082
|
+
h(this, "overlay", null);
|
|
2083
|
+
h(this, "chartContainer", null);
|
|
2084
|
+
h(this, "loadingIndicator", null);
|
|
2085
|
+
h(this, "state", "hidden");
|
|
2086
|
+
h(this, "resizeObserver", null);
|
|
2087
|
+
h(this, "events");
|
|
2088
|
+
h(this, "dimensions");
|
|
2089
|
+
h(this, "keyHandler", null);
|
|
2090
|
+
h(this, "clickHandler", null);
|
|
2091
|
+
/** Transition duration in milliseconds */
|
|
2092
|
+
h(this, "TRANSITION_DURATION", 200);
|
|
2093
|
+
this.container = t, this.events = e, this.dimensions = i;
|
|
2094
|
+
}
|
|
2095
|
+
/**
|
|
2096
|
+
* Get current overlay state.
|
|
2097
|
+
* @returns The current state of the overlay (hidden, showing, visible, hiding)
|
|
2098
|
+
*/
|
|
2099
|
+
getState() {
|
|
2100
|
+
return this.state;
|
|
2101
|
+
}
|
|
2102
|
+
/**
|
|
2103
|
+
* Check if overlay is currently visible or in the process of showing.
|
|
2104
|
+
* @returns True if the overlay is visible or transitioning to visible
|
|
2105
|
+
*/
|
|
2106
|
+
isVisible() {
|
|
2107
|
+
return this.state === "visible" || this.state === "showing";
|
|
2108
|
+
}
|
|
2109
|
+
/**
|
|
2110
|
+
* Get the chart container element where uPlot should render.
|
|
2111
|
+
* @returns The chart container div element, or null if overlay is not shown
|
|
2112
|
+
*/
|
|
2113
|
+
getChartContainer() {
|
|
2114
|
+
return this.chartContainer;
|
|
2115
|
+
}
|
|
2116
|
+
/**
|
|
2117
|
+
* Get computed chart dimensions based on container size.
|
|
2118
|
+
* Respects minimum dimensions and percentage-based sizing.
|
|
2119
|
+
* @returns Object with width and height in pixels
|
|
2120
|
+
*/
|
|
2121
|
+
getChartDimensions() {
|
|
2122
|
+
const t = this.container.getBoundingClientRect(), e = Math.max(
|
|
2123
|
+
this.dimensions.minWidth,
|
|
2124
|
+
Math.floor(
|
|
2125
|
+
t.width * this.dimensions.widthPercent / 100 - this.dimensions.padding * 2
|
|
2126
|
+
)
|
|
2127
|
+
), i = Math.max(
|
|
2128
|
+
this.dimensions.minHeight,
|
|
2129
|
+
Math.floor(
|
|
2130
|
+
t.height * this.dimensions.heightPercent / 100 - this.dimensions.padding * 2
|
|
2131
|
+
)
|
|
2132
|
+
);
|
|
2133
|
+
return { width: e, height: i };
|
|
2134
|
+
}
|
|
2135
|
+
/**
|
|
2136
|
+
* Show the overlay with fade-in animation.
|
|
2137
|
+
* Sets up keyboard handlers, click handlers, and resize observers.
|
|
2138
|
+
* @returns Promise that resolves when the transition completes
|
|
2139
|
+
*/
|
|
2140
|
+
show() {
|
|
2141
|
+
return this.state === "visible" || this.state === "showing" ? Promise.resolve() : (this.state = "showing", this.createOverlay(), this.setupKeyboardHandler(), this.setupResizeObserver(), new Promise((t) => {
|
|
2142
|
+
this.overlay?.offsetHeight, this.overlay && (this.overlay.style.opacity = "1"), setTimeout(() => {
|
|
2143
|
+
this.state = "visible", t();
|
|
2144
|
+
}, this.TRANSITION_DURATION);
|
|
2145
|
+
}));
|
|
2146
|
+
}
|
|
2147
|
+
/**
|
|
2148
|
+
* Hide the overlay with fade-out animation.
|
|
2149
|
+
* Cleans up all event handlers and removes DOM elements.
|
|
2150
|
+
* @returns Promise that resolves when the transition completes
|
|
2151
|
+
*/
|
|
2152
|
+
hide() {
|
|
2153
|
+
return this.state === "hidden" || this.state === "hiding" ? Promise.resolve() : (this.state = "hiding", this.overlay && (this.overlay.style.opacity = "0"), new Promise((t) => {
|
|
2154
|
+
setTimeout(() => {
|
|
2155
|
+
this.destroyOverlay(), this.state = "hidden", t();
|
|
2156
|
+
}, this.TRANSITION_DURATION);
|
|
2157
|
+
}));
|
|
2158
|
+
}
|
|
2159
|
+
/**
|
|
2160
|
+
* Toggle overlay visibility.
|
|
2161
|
+
* Shows the overlay if hidden, hides it if visible.
|
|
2162
|
+
* @returns Promise that resolves when the transition completes
|
|
2163
|
+
*/
|
|
2164
|
+
toggle() {
|
|
2165
|
+
return this.isVisible() ? this.hide() : this.show();
|
|
2166
|
+
}
|
|
2167
|
+
/**
|
|
2168
|
+
* Show loading indicator while chart library is being fetched.
|
|
2169
|
+
* Displays a spinner and "Loading chart library..." message.
|
|
2170
|
+
*/
|
|
2171
|
+
showLoading() {
|
|
2172
|
+
if (!this.chartContainer || this.loadingIndicator) return;
|
|
2173
|
+
this.loadingIndicator = document.createElement("div"), this.loadingIndicator.className = "duckdb-chart-loading", this.loadingIndicator.style.cssText = `
|
|
2174
|
+
position: absolute;
|
|
2175
|
+
top: 50%;
|
|
2176
|
+
left: 50%;
|
|
2177
|
+
transform: translate(-50%, -50%);
|
|
2178
|
+
color: rgba(255, 255, 255, 0.7);
|
|
2179
|
+
font-size: 14px;
|
|
2180
|
+
font-family: system-ui, sans-serif;
|
|
2181
|
+
display: flex;
|
|
2182
|
+
flex-direction: column;
|
|
2183
|
+
align-items: center;
|
|
2184
|
+
gap: 12px;
|
|
2185
|
+
`;
|
|
2186
|
+
const t = document.createElement("div");
|
|
2187
|
+
if (t.style.cssText = `
|
|
2188
|
+
width: 32px;
|
|
2189
|
+
height: 32px;
|
|
2190
|
+
border: 3px solid rgba(255, 255, 255, 0.2);
|
|
2191
|
+
border-top-color: rgba(255, 255, 255, 0.8);
|
|
2192
|
+
border-radius: 50%;
|
|
2193
|
+
animation: duckdb-chart-spin 0.8s linear infinite;
|
|
2194
|
+
`, !document.getElementById("duckdb-chart-spinner-styles")) {
|
|
2195
|
+
const i = document.createElement("style");
|
|
2196
|
+
i.id = "duckdb-chart-spinner-styles", i.textContent = `
|
|
2197
|
+
@keyframes duckdb-chart-spin {
|
|
2198
|
+
to { transform: rotate(360deg); }
|
|
2199
|
+
}
|
|
2200
|
+
`, document.head.appendChild(i);
|
|
2201
|
+
}
|
|
2202
|
+
const e = document.createElement("span");
|
|
2203
|
+
e.textContent = "Loading chart library...", this.loadingIndicator.appendChild(t), this.loadingIndicator.appendChild(e), this.chartContainer.appendChild(this.loadingIndicator);
|
|
2204
|
+
}
|
|
2205
|
+
/**
|
|
2206
|
+
* Hide loading indicator after chart library has loaded.
|
|
2207
|
+
*/
|
|
2208
|
+
hideLoading() {
|
|
2209
|
+
this.loadingIndicator && this.loadingIndicator.parentNode && this.loadingIndicator.parentNode.removeChild(this.loadingIndicator), this.loadingIndicator = null;
|
|
2210
|
+
}
|
|
2211
|
+
/**
|
|
2212
|
+
* Clean up all resources and remove overlay from DOM.
|
|
2213
|
+
* Should be called when the overlay is no longer needed.
|
|
2214
|
+
*/
|
|
2215
|
+
destroy() {
|
|
2216
|
+
this.destroyOverlay(), this.state = "hidden";
|
|
2217
|
+
}
|
|
2218
|
+
/**
|
|
2219
|
+
* Create the overlay DOM structure
|
|
2220
|
+
*/
|
|
2221
|
+
createOverlay() {
|
|
2222
|
+
if (this.overlay) return;
|
|
2223
|
+
this.overlay = document.createElement("div"), this.overlay.className = "duckdb-chart-overlay", this.overlay.style.cssText = `
|
|
2224
|
+
position: absolute;
|
|
2225
|
+
top: 0;
|
|
2226
|
+
left: 0;
|
|
2227
|
+
right: 0;
|
|
2228
|
+
bottom: 0;
|
|
2229
|
+
background: rgba(0, 0, 0, 0.85);
|
|
2230
|
+
display: flex;
|
|
2231
|
+
align-items: center;
|
|
2232
|
+
justify-content: center;
|
|
2233
|
+
z-index: 1000;
|
|
2234
|
+
opacity: 0;
|
|
2235
|
+
transition: opacity ${this.TRANSITION_DURATION}ms ease-in-out;
|
|
2236
|
+
`, this.chartContainer = document.createElement("div"), this.chartContainer.className = "duckdb-chart-container";
|
|
2237
|
+
const { width: t, height: e } = this.getChartDimensions();
|
|
2238
|
+
this.chartContainer.style.cssText = `
|
|
2239
|
+
width: ${t}px;
|
|
2240
|
+
height: ${e}px;
|
|
2241
|
+
background: var(--chart-bg, #1e1e1e);
|
|
2242
|
+
border-radius: 8px;
|
|
2243
|
+
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
|
|
2244
|
+
padding: ${this.dimensions.padding}px;
|
|
2245
|
+
box-sizing: border-box;
|
|
2246
|
+
`;
|
|
2247
|
+
const i = document.createElement("div");
|
|
2248
|
+
i.className = "duckdb-chart-close-hint", i.style.cssText = `
|
|
2249
|
+
position: absolute;
|
|
2250
|
+
top: 12px;
|
|
2251
|
+
right: 16px;
|
|
2252
|
+
color: rgba(255, 255, 255, 0.5);
|
|
2253
|
+
font-size: 12px;
|
|
2254
|
+
font-family: system-ui, sans-serif;
|
|
2255
|
+
`, i.textContent = "ESC or click outside to close", this.overlay.appendChild(this.chartContainer), this.overlay.appendChild(i), this.setupClickHandler(), getComputedStyle(this.container).position === "static" && (this.container.style.position = "relative"), this.container.appendChild(this.overlay);
|
|
2256
|
+
}
|
|
2257
|
+
/**
|
|
2258
|
+
* Remove overlay from DOM and clean up
|
|
2259
|
+
*/
|
|
2260
|
+
destroyOverlay() {
|
|
2261
|
+
this.removeKeyboardHandler(), this.removeClickHandler(), this.removeResizeObserver(), this.overlay && this.overlay.parentNode && this.overlay.parentNode.removeChild(this.overlay), this.overlay = null, this.chartContainer = null;
|
|
2262
|
+
}
|
|
2263
|
+
/**
|
|
2264
|
+
* Set up keyboard event handler for ESC and Ctrl+S
|
|
2265
|
+
*/
|
|
2266
|
+
setupKeyboardHandler() {
|
|
2267
|
+
this.keyHandler = (t) => {
|
|
2268
|
+
t.key === "Escape" ? (t.preventDefault(), t.stopPropagation(), this.hide(), this.events.onDismiss?.()) : t.key === "s" && (t.ctrlKey || t.metaKey) && (t.preventDefault(), t.stopPropagation(), this.events.onExport?.());
|
|
2269
|
+
}, document.addEventListener("keydown", this.keyHandler, !0);
|
|
2270
|
+
}
|
|
2271
|
+
/**
|
|
2272
|
+
* Remove keyboard event handler
|
|
2273
|
+
*/
|
|
2274
|
+
removeKeyboardHandler() {
|
|
2275
|
+
this.keyHandler && (document.removeEventListener("keydown", this.keyHandler, !0), this.keyHandler = null);
|
|
2276
|
+
}
|
|
2277
|
+
/**
|
|
2278
|
+
* Set up click handler to close overlay when clicking outside the chart
|
|
2279
|
+
*/
|
|
2280
|
+
setupClickHandler() {
|
|
2281
|
+
this.clickHandler = (t) => {
|
|
2282
|
+
t.target === this.overlay && (t.preventDefault(), t.stopPropagation(), this.hide(), this.events.onDismiss?.());
|
|
2283
|
+
}, this.overlay?.addEventListener("click", this.clickHandler);
|
|
2284
|
+
}
|
|
2285
|
+
/**
|
|
2286
|
+
* Remove click handler
|
|
2287
|
+
*/
|
|
2288
|
+
removeClickHandler() {
|
|
2289
|
+
this.clickHandler && this.overlay && this.overlay.removeEventListener("click", this.clickHandler), this.clickHandler = null;
|
|
2290
|
+
}
|
|
2291
|
+
/**
|
|
2292
|
+
* Set up resize observer to update chart dimensions
|
|
2293
|
+
*/
|
|
2294
|
+
setupResizeObserver() {
|
|
2295
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
2296
|
+
if (this.chartContainer && this.state === "visible") {
|
|
2297
|
+
const { width: t, height: e } = this.getChartDimensions();
|
|
2298
|
+
this.chartContainer.style.width = `${t}px`, this.chartContainer.style.height = `${e}px`;
|
|
2299
|
+
}
|
|
2300
|
+
}), this.resizeObserver.observe(this.container);
|
|
2301
|
+
}
|
|
2302
|
+
/**
|
|
2303
|
+
* Remove resize observer
|
|
2304
|
+
*/
|
|
2305
|
+
removeResizeObserver() {
|
|
2306
|
+
this.resizeObserver && (this.resizeObserver.disconnect(), this.resizeObserver = null);
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
const Fe = /* @__PURE__ */ new Set([
|
|
2310
|
+
"INTEGER",
|
|
2311
|
+
"BIGINT",
|
|
2312
|
+
"SMALLINT",
|
|
2313
|
+
"TINYINT",
|
|
2314
|
+
"UTINYINT",
|
|
2315
|
+
"USMALLINT",
|
|
2316
|
+
"UINTEGER",
|
|
2317
|
+
"UBIGINT",
|
|
2318
|
+
"HUGEINT",
|
|
2319
|
+
"UHUGEINT",
|
|
2320
|
+
"FLOAT",
|
|
2321
|
+
"DOUBLE",
|
|
2322
|
+
"DECIMAL",
|
|
2323
|
+
"REAL",
|
|
2324
|
+
"INT",
|
|
2325
|
+
"INT1",
|
|
2326
|
+
"INT2",
|
|
2327
|
+
"INT4",
|
|
2328
|
+
"INT8",
|
|
2329
|
+
"INT16",
|
|
2330
|
+
"INT32",
|
|
2331
|
+
"INT64",
|
|
2332
|
+
"INT128",
|
|
2333
|
+
"UINT8",
|
|
2334
|
+
"UINT16",
|
|
2335
|
+
"UINT32",
|
|
2336
|
+
"UINT64",
|
|
2337
|
+
"UINT128",
|
|
2338
|
+
"FLOAT4",
|
|
2339
|
+
"FLOAT8",
|
|
2340
|
+
"NUMERIC"
|
|
2341
|
+
]), He = /* @__PURE__ */ new Set([
|
|
2342
|
+
"DATE",
|
|
2343
|
+
"TIME",
|
|
2344
|
+
"TIMESTAMP",
|
|
2345
|
+
"TIMESTAMPTZ",
|
|
2346
|
+
"TIMESTAMP_S",
|
|
2347
|
+
"TIMESTAMP_MS",
|
|
2348
|
+
"TIMESTAMP_NS",
|
|
2349
|
+
"TIMESTAMP WITH TIME ZONE",
|
|
2350
|
+
"TIME WITH TIME ZONE",
|
|
2351
|
+
"TIMETZ",
|
|
2352
|
+
"INTERVAL"
|
|
2353
|
+
]), Qe = /* @__PURE__ */ new Set([
|
|
2354
|
+
"VARCHAR",
|
|
2355
|
+
"TEXT",
|
|
2356
|
+
"STRING",
|
|
2357
|
+
"CHAR",
|
|
2358
|
+
"BPCHAR",
|
|
2359
|
+
"ENUM",
|
|
2360
|
+
"BOOLEAN",
|
|
2361
|
+
"BOOL",
|
|
2362
|
+
"UUID"
|
|
2363
|
+
]), Pt = 20;
|
|
2364
|
+
function _e(s, t) {
|
|
2365
|
+
if (s) {
|
|
2366
|
+
const e = s.toUpperCase().split("(")[0].split("<")[0].trim();
|
|
2367
|
+
if (Fe.has(e))
|
|
2368
|
+
return "numeric";
|
|
2369
|
+
if (He.has(e))
|
|
2370
|
+
return "temporal";
|
|
2371
|
+
if (Qe.has(e))
|
|
2372
|
+
return "categorical";
|
|
2373
|
+
}
|
|
2374
|
+
return ze(t);
|
|
2375
|
+
}
|
|
2376
|
+
function ze(s) {
|
|
2377
|
+
const t = s.filter((i) => i != null);
|
|
2378
|
+
if (t.length === 0)
|
|
2379
|
+
return "unknown";
|
|
2380
|
+
const e = t.slice(0, 100);
|
|
2381
|
+
return e.every((i) => typeof i == "number" || typeof i == "bigint") ? "numeric" : e.every(
|
|
2382
|
+
(i) => i instanceof Date || typeof i == "string" && !isNaN(Date.parse(i)) && qe(i)
|
|
2383
|
+
) ? "temporal" : e.every((i) => typeof i == "string" || typeof i == "boolean") ? "categorical" : "unknown";
|
|
2384
|
+
}
|
|
2385
|
+
function qe(s) {
|
|
2386
|
+
return [
|
|
2387
|
+
/^\d{4}-\d{2}-\d{2}/,
|
|
2388
|
+
// ISO date
|
|
2389
|
+
/^\d{2}\/\d{2}\/\d{4}/,
|
|
2390
|
+
// US date
|
|
2391
|
+
/^\d{2}\.\d{2}\.\d{4}/,
|
|
2392
|
+
// EU date
|
|
2393
|
+
/^\d{4}\/\d{2}\/\d{2}/
|
|
2394
|
+
// Alt ISO
|
|
2395
|
+
].some((e) => e.test(s));
|
|
2396
|
+
}
|
|
2397
|
+
function kt(s, t, e) {
|
|
2398
|
+
return s.map((i, r) => {
|
|
2399
|
+
const n = t.map((c) => c[r]), o = e?.[r], a = _e(o, n), l = new Set(n.filter((c) => c != null));
|
|
2400
|
+
return {
|
|
2401
|
+
name: i,
|
|
2402
|
+
type: a,
|
|
2403
|
+
duckdbType: o,
|
|
2404
|
+
uniqueCount: l.size
|
|
2405
|
+
};
|
|
2406
|
+
});
|
|
2407
|
+
}
|
|
2408
|
+
function je(s) {
|
|
2409
|
+
const t = s.filter((r) => r.type === "numeric"), e = s.filter((r) => r.type === "temporal"), i = s.filter(
|
|
2410
|
+
(r) => r.type === "categorical" && (r.uniqueCount ?? 0) <= Pt
|
|
2411
|
+
);
|
|
2412
|
+
return s.length === 1 && t.length === 1 ? "histogram" : e.length >= 1 && t.length >= 1 ? "line" : i.length >= 1 && t.length >= 1 ? "bar" : t.length === 2 && s.length === 2 ? "scatter" : (t.length >= 2, "line");
|
|
2413
|
+
}
|
|
2414
|
+
function We(s, t) {
|
|
2415
|
+
if (t?.x || t?.y) {
|
|
2416
|
+
const l = t.x ?? null, c = t.y ?? s.filter((u) => u.type === "numeric" && u.name !== l).map((u) => u.name);
|
|
2417
|
+
let f;
|
|
2418
|
+
return l && s.find((p) => p.name === l)?.type === "categorical" && (f = []), { xColumn: l, yColumns: c, xLabels: f };
|
|
2419
|
+
}
|
|
2420
|
+
const e = s.filter((l) => l.type === "temporal"), i = s.filter(
|
|
2421
|
+
(l) => l.type === "categorical" && (l.uniqueCount ?? 0) <= Pt
|
|
2422
|
+
), r = s.filter((l) => l.type === "numeric");
|
|
2423
|
+
let n = null, o;
|
|
2424
|
+
e.length > 0 ? n = e[0].name : i.length > 0 && (n = i[0].name, o = []);
|
|
2425
|
+
const a = r.filter((l) => l.name !== n).map((l) => l.name);
|
|
2426
|
+
return { xColumn: n, yColumns: a, xLabels: o };
|
|
2427
|
+
}
|
|
2428
|
+
function Ge(s, t) {
|
|
2429
|
+
const e = new Set(t.map((r) => r.name)), i = s.filter((r) => !e.has(r));
|
|
2430
|
+
return {
|
|
2431
|
+
valid: i.length === 0,
|
|
2432
|
+
missing: i
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
function ht(s, t = {}) {
|
|
2436
|
+
const { columns: e, rows: i } = s, r = kt(e, i, t.duckdbTypes), n = We(r, {
|
|
2437
|
+
x: t.x,
|
|
2438
|
+
y: t.y
|
|
2439
|
+
}), o = t.type ?? je(r);
|
|
2440
|
+
let a, l;
|
|
2441
|
+
if (o === "histogram") {
|
|
2442
|
+
const u = Ke(i, r);
|
|
2443
|
+
a = u.data, l = u.seriesNames, n.xLabels = u.labels;
|
|
2444
|
+
} else {
|
|
2445
|
+
const u = Ve(i, r, n);
|
|
2446
|
+
a = u.data, l = u.seriesNames, u.xLabels && (n.xLabels = u.xLabels);
|
|
2447
|
+
}
|
|
2448
|
+
const f = (n.xColumn ? r.find((u) => u.name === n.xColumn) : null)?.type === "temporal";
|
|
2449
|
+
return {
|
|
2450
|
+
data: a,
|
|
2451
|
+
chartType: o,
|
|
2452
|
+
columns: r,
|
|
2453
|
+
axes: n,
|
|
2454
|
+
seriesNames: l,
|
|
2455
|
+
isXTemporal: f
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
function Ve(s, t, e) {
|
|
2459
|
+
const i = new Map(t.map((a, l) => [a.name, l]));
|
|
2460
|
+
let r, n;
|
|
2461
|
+
if (e.xColumn) {
|
|
2462
|
+
const a = i.get(e.xColumn), l = t.find((c) => c.name === e.xColumn);
|
|
2463
|
+
if (a === void 0)
|
|
2464
|
+
throw new Error(`X column '${e.xColumn}' not found`);
|
|
2465
|
+
if (l?.type === "temporal")
|
|
2466
|
+
r = s.map((c) => Xe(c[a]));
|
|
2467
|
+
else if (l?.type === "categorical") {
|
|
2468
|
+
const c = [], f = /* @__PURE__ */ new Set();
|
|
2469
|
+
for (const p of s) {
|
|
2470
|
+
const g = String(p[a]);
|
|
2471
|
+
f.has(g) || (f.add(g), c.push(g));
|
|
2472
|
+
}
|
|
2473
|
+
n = c;
|
|
2474
|
+
const u = Ye(
|
|
2475
|
+
s,
|
|
2476
|
+
a,
|
|
2477
|
+
e.yColumns,
|
|
2478
|
+
i,
|
|
2479
|
+
c
|
|
2480
|
+
);
|
|
2481
|
+
return {
|
|
2482
|
+
data: [u.xValues, ...u.yColumns],
|
|
2483
|
+
seriesNames: e.yColumns,
|
|
2484
|
+
xLabels: n
|
|
2485
|
+
};
|
|
2486
|
+
} else
|
|
2487
|
+
r = s.map((c) => W(c[a]));
|
|
2488
|
+
} else
|
|
2489
|
+
r = s.map((a, l) => l);
|
|
2490
|
+
const o = e.yColumns.map((a) => {
|
|
2491
|
+
const l = i.get(a);
|
|
2492
|
+
if (l === void 0)
|
|
2493
|
+
throw new Error(`Y column '${a}' not found`);
|
|
2494
|
+
return s.map((c) => W(c[l]));
|
|
2495
|
+
});
|
|
2496
|
+
return {
|
|
2497
|
+
data: [r, ...o],
|
|
2498
|
+
seriesNames: e.yColumns,
|
|
2499
|
+
xLabels: n
|
|
2500
|
+
};
|
|
2501
|
+
}
|
|
2502
|
+
function Ye(s, t, e, i, r) {
|
|
2503
|
+
const n = e.map(
|
|
2504
|
+
() => /* @__PURE__ */ new Map()
|
|
2505
|
+
);
|
|
2506
|
+
for (const l of r)
|
|
2507
|
+
for (const c of n)
|
|
2508
|
+
c.set(l, 0);
|
|
2509
|
+
for (const l of s) {
|
|
2510
|
+
const c = String(l[t]);
|
|
2511
|
+
e.forEach((f, u) => {
|
|
2512
|
+
const p = i.get(f);
|
|
2513
|
+
if (p !== void 0) {
|
|
2514
|
+
const g = W(l[p]);
|
|
2515
|
+
if (g !== null) {
|
|
2516
|
+
const y = n[u].get(c) ?? 0;
|
|
2517
|
+
n[u].set(c, y + g);
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
});
|
|
2521
|
+
}
|
|
2522
|
+
const o = r.map((l, c) => c), a = n.map(
|
|
2523
|
+
(l) => r.map((c) => l.get(c) ?? null)
|
|
2524
|
+
);
|
|
2525
|
+
return { xValues: o, yColumns: a };
|
|
2526
|
+
}
|
|
2527
|
+
function Ke(s, t, e) {
|
|
2528
|
+
const i = t.find((g) => g.type === "numeric");
|
|
2529
|
+
if (!i)
|
|
2530
|
+
throw new Error("No numeric column found for histogram");
|
|
2531
|
+
const r = t.findIndex((g) => g.name === i.name), n = s.map((g) => W(g[r])).filter((g) => g !== null);
|
|
2532
|
+
if (n.length === 0)
|
|
2533
|
+
return {
|
|
2534
|
+
data: [[], []],
|
|
2535
|
+
seriesNames: ["Count"],
|
|
2536
|
+
labels: []
|
|
2537
|
+
};
|
|
2538
|
+
const o = Math.ceil(Math.log2(n.length) + 1), a = Math.min(...n), c = (Math.max(...n) - a) / o || 1, f = new Array(o).fill(0), u = [];
|
|
2539
|
+
for (let g = 0; g < o; g++) {
|
|
2540
|
+
const y = a + g * c, x = y + c;
|
|
2541
|
+
u.push(`${ut(y)}-${ut(x)}`);
|
|
2542
|
+
}
|
|
2543
|
+
for (const g of n) {
|
|
2544
|
+
let y = Math.floor((g - a) / c);
|
|
2545
|
+
y >= o && (y = o - 1), f[y]++;
|
|
2546
|
+
}
|
|
2547
|
+
return {
|
|
2548
|
+
data: [u.map((g, y) => y), f],
|
|
2549
|
+
seriesNames: ["Count"],
|
|
2550
|
+
labels: u
|
|
2551
|
+
};
|
|
2552
|
+
}
|
|
2553
|
+
function Xe(s) {
|
|
2554
|
+
if (s == null) return null;
|
|
2555
|
+
if (s instanceof Date)
|
|
2556
|
+
return s.getTime() / 1e3;
|
|
2557
|
+
if (typeof s == "number")
|
|
2558
|
+
return s > 1e12 ? s / 1e3 : s;
|
|
2559
|
+
if (typeof s == "string") {
|
|
2560
|
+
const t = Date.parse(s);
|
|
2561
|
+
if (!isNaN(t))
|
|
2562
|
+
return t / 1e3;
|
|
2563
|
+
}
|
|
2564
|
+
return null;
|
|
2565
|
+
}
|
|
2566
|
+
function W(s) {
|
|
2567
|
+
if (s == null) return null;
|
|
2568
|
+
if (typeof s == "number")
|
|
2569
|
+
return isNaN(s) ? null : s;
|
|
2570
|
+
if (typeof s == "bigint")
|
|
2571
|
+
return Number(s);
|
|
2572
|
+
if (typeof s == "string") {
|
|
2573
|
+
const t = parseFloat(s);
|
|
2574
|
+
return isNaN(t) ? null : t;
|
|
2575
|
+
}
|
|
2576
|
+
return null;
|
|
2577
|
+
}
|
|
2578
|
+
function ut(s) {
|
|
2579
|
+
return Number.isInteger(s) ? s.toString() : s.toFixed(2);
|
|
2580
|
+
}
|
|
2581
|
+
function Je(s) {
|
|
2582
|
+
return s ? s.rows.length === 0 ? { valid: !1, error: "Query returned no rows." } : kt(s.columns, s.rows).filter((i) => i.type === "numeric").length === 0 ? { valid: !1, error: "No numeric columns found for charting." } : { valid: !0 } : { valid: !1, error: "No data to chart. Run a query first." };
|
|
2583
|
+
}
|
|
2584
|
+
function Ze(s) {
|
|
2585
|
+
return {
|
|
2586
|
+
background: s.background,
|
|
2587
|
+
foreground: s.foreground,
|
|
2588
|
+
gridColor: dt(s.foreground, 0.2),
|
|
2589
|
+
axisColor: dt(s.foreground, 0.6),
|
|
2590
|
+
seriesColors: ti(s),
|
|
2591
|
+
tooltipBg: ei(s.background, 0.95),
|
|
2592
|
+
tooltipFg: s.foreground
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
function ti(s) {
|
|
2596
|
+
return [
|
|
2597
|
+
s.blue,
|
|
2598
|
+
s.green,
|
|
2599
|
+
s.red,
|
|
2600
|
+
s.yellow,
|
|
2601
|
+
s.magenta,
|
|
2602
|
+
s.cyan,
|
|
2603
|
+
s.brightBlue,
|
|
2604
|
+
s.brightGreen,
|
|
2605
|
+
s.brightRed,
|
|
2606
|
+
s.brightYellow,
|
|
2607
|
+
s.brightMagenta,
|
|
2608
|
+
s.brightCyan
|
|
2609
|
+
];
|
|
2610
|
+
}
|
|
2611
|
+
function dt(s, t) {
|
|
2612
|
+
const e = It(s);
|
|
2613
|
+
return e ? `rgba(${e.r}, ${e.g}, ${e.b}, ${t})` : s;
|
|
2614
|
+
}
|
|
2615
|
+
function ei(s, t) {
|
|
2616
|
+
const e = It(s);
|
|
2617
|
+
return e ? `rgba(${e.r}, ${e.g}, ${e.b}, ${t})` : s;
|
|
2618
|
+
}
|
|
2619
|
+
function It(s) {
|
|
2620
|
+
if (s.startsWith("#")) {
|
|
2621
|
+
const e = s.slice(1);
|
|
2622
|
+
if (e.length === 3)
|
|
2623
|
+
return {
|
|
2624
|
+
r: parseInt(e[0] + e[0], 16),
|
|
2625
|
+
g: parseInt(e[1] + e[1], 16),
|
|
2626
|
+
b: parseInt(e[2] + e[2], 16)
|
|
2627
|
+
};
|
|
2628
|
+
if (e.length === 6)
|
|
2629
|
+
return {
|
|
2630
|
+
r: parseInt(e.slice(0, 2), 16),
|
|
2631
|
+
g: parseInt(e.slice(2, 4), 16),
|
|
2632
|
+
b: parseInt(e.slice(4, 6), 16)
|
|
2633
|
+
};
|
|
2634
|
+
}
|
|
2635
|
+
const t = s.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
|
2636
|
+
return t ? {
|
|
2637
|
+
r: parseInt(t[1], 10),
|
|
2638
|
+
g: parseInt(t[2], 10),
|
|
2639
|
+
b: parseInt(t[3], 10)
|
|
2640
|
+
} : null;
|
|
2641
|
+
}
|
|
2642
|
+
const ii = {
|
|
2643
|
+
background: "#1e1e1e",
|
|
2644
|
+
foreground: "#d4d4d4",
|
|
2645
|
+
gridColor: "rgba(212, 212, 212, 0.2)",
|
|
2646
|
+
axisColor: "rgba(212, 212, 212, 0.6)",
|
|
2647
|
+
seriesColors: [
|
|
2648
|
+
"#2472c8",
|
|
2649
|
+
// blue
|
|
2650
|
+
"#0dbc79",
|
|
2651
|
+
// green
|
|
2652
|
+
"#cd3131",
|
|
2653
|
+
// red
|
|
2654
|
+
"#e5e510",
|
|
2655
|
+
// yellow
|
|
2656
|
+
"#bc3fbc",
|
|
2657
|
+
// magenta
|
|
2658
|
+
"#11a8cd",
|
|
2659
|
+
// cyan
|
|
2660
|
+
"#3b8eea",
|
|
2661
|
+
// bright blue
|
|
2662
|
+
"#23d18b",
|
|
2663
|
+
// bright green
|
|
2664
|
+
"#f14c4c",
|
|
2665
|
+
// bright red
|
|
2666
|
+
"#f5f543",
|
|
2667
|
+
// bright yellow
|
|
2668
|
+
"#d670d6",
|
|
2669
|
+
// bright magenta
|
|
2670
|
+
"#29b8db"
|
|
2671
|
+
// bright cyan
|
|
2672
|
+
],
|
|
2673
|
+
tooltipBg: "rgba(30, 30, 30, 0.95)",
|
|
2674
|
+
tooltipFg: "#d4d4d4"
|
|
2675
|
+
}, si = {
|
|
2676
|
+
background: "#ffffff",
|
|
2677
|
+
foreground: "#1e1e1e",
|
|
2678
|
+
gridColor: "rgba(30, 30, 30, 0.15)",
|
|
2679
|
+
axisColor: "rgba(30, 30, 30, 0.6)",
|
|
2680
|
+
seriesColors: [
|
|
2681
|
+
"#0066cc",
|
|
2682
|
+
// blue
|
|
2683
|
+
"#008844",
|
|
2684
|
+
// green
|
|
2685
|
+
"#cc0000",
|
|
2686
|
+
// red
|
|
2687
|
+
"#cc9900",
|
|
2688
|
+
// yellow/gold
|
|
2689
|
+
"#9933cc",
|
|
2690
|
+
// magenta
|
|
2691
|
+
"#0099aa",
|
|
2692
|
+
// cyan
|
|
2693
|
+
"#3399ff",
|
|
2694
|
+
// bright blue
|
|
2695
|
+
"#33cc66",
|
|
2696
|
+
// bright green
|
|
2697
|
+
"#ff3333",
|
|
2698
|
+
// bright red
|
|
2699
|
+
"#ffcc00",
|
|
2700
|
+
// bright yellow
|
|
2701
|
+
"#cc66ff",
|
|
2702
|
+
// bright magenta
|
|
2703
|
+
"#33cccc"
|
|
2704
|
+
// bright cyan
|
|
2705
|
+
],
|
|
2706
|
+
tooltipBg: "rgba(255, 255, 255, 0.95)",
|
|
2707
|
+
tooltipFg: "#1e1e1e"
|
|
2708
|
+
};
|
|
2709
|
+
function ri(s) {
|
|
2710
|
+
return s === "dark" ? ii : si;
|
|
2711
|
+
}
|
|
2712
|
+
class ni {
|
|
2713
|
+
/**
|
|
2714
|
+
* Creates a new ChartRenderer instance.
|
|
2715
|
+
* @param uPlotConstructor - The uPlot constructor function (loaded from CDN)
|
|
2716
|
+
* @param container - The HTML element to render the chart into
|
|
2717
|
+
*/
|
|
2718
|
+
constructor(t, e) {
|
|
2719
|
+
h(this, "uPlot");
|
|
2720
|
+
h(this, "chart", null);
|
|
2721
|
+
h(this, "container");
|
|
2722
|
+
this.uPlot = t, this.container = e;
|
|
2723
|
+
}
|
|
2724
|
+
/**
|
|
2725
|
+
* Render a chart with the given data and options.
|
|
2726
|
+
* Destroys any existing chart before creating a new one.
|
|
2727
|
+
* @param data - The chart data in uPlot columnar format (first array is X, rest are Y series)
|
|
2728
|
+
* @param options - Rendering options including type, theme, dimensions, and series names
|
|
2729
|
+
* @returns The created uPlot instance
|
|
2730
|
+
*/
|
|
2731
|
+
render(t, e) {
|
|
2732
|
+
this.destroy();
|
|
2733
|
+
const i = this.buildOptions(e);
|
|
2734
|
+
return this.chart = new this.uPlot(i, t, this.container), e.seriesNames.length > 0 && this.createInlineLegend(e.seriesNames, e.theme), this.chart;
|
|
2735
|
+
}
|
|
2736
|
+
/**
|
|
2737
|
+
* Update chart with new data while keeping the same options.
|
|
2738
|
+
* @param data - The new chart data in uPlot columnar format
|
|
2739
|
+
*/
|
|
2740
|
+
update(t) {
|
|
2741
|
+
this.chart && this.chart.setData(t);
|
|
2742
|
+
}
|
|
2743
|
+
/**
|
|
2744
|
+
* Resize chart to new dimensions.
|
|
2745
|
+
* @param width - New width in pixels
|
|
2746
|
+
* @param height - New height in pixels
|
|
2747
|
+
*/
|
|
2748
|
+
resize(t, e) {
|
|
2749
|
+
this.chart && this.chart.setSize({ width: t, height: e });
|
|
2750
|
+
}
|
|
2751
|
+
/**
|
|
2752
|
+
* Get the underlying uPlot instance.
|
|
2753
|
+
* @returns The uPlot instance, or null if no chart is rendered
|
|
2754
|
+
*/
|
|
2755
|
+
getChart() {
|
|
2756
|
+
return this.chart;
|
|
2757
|
+
}
|
|
2758
|
+
/**
|
|
2759
|
+
* Get the chart canvas element for export operations.
|
|
2760
|
+
* @returns The canvas element, or null if no chart is rendered
|
|
2761
|
+
*/
|
|
2762
|
+
getCanvas() {
|
|
2763
|
+
return this.chart?.ctx?.canvas ?? null;
|
|
2764
|
+
}
|
|
2765
|
+
/**
|
|
2766
|
+
* Destroy the chart and clean up resources.
|
|
2767
|
+
* Removes the legend and destroys the uPlot instance.
|
|
2768
|
+
*/
|
|
2769
|
+
destroy() {
|
|
2770
|
+
const t = this.container.querySelector(".uplot-legend-below");
|
|
2771
|
+
t && t.remove(), this.chart && (this.chart.destroy(), this.chart = null);
|
|
2772
|
+
}
|
|
2773
|
+
/**
|
|
2774
|
+
* Build uPlot options from render options.
|
|
2775
|
+
* Configures scales, axes, series, cursor, and plugins.
|
|
2776
|
+
* @param options - The render options
|
|
2777
|
+
* @returns uPlot configuration options
|
|
2778
|
+
*/
|
|
2779
|
+
buildOptions(t) {
|
|
2780
|
+
const { type: e, theme: i, width: r, height: n, seriesNames: o, axes: a, xLabels: l, isXTemporal: c } = t;
|
|
2781
|
+
return {
|
|
2782
|
+
width: r,
|
|
2783
|
+
height: n,
|
|
2784
|
+
...this.getChartPadding(),
|
|
2785
|
+
cursor: this.getCursorOptions(i),
|
|
2786
|
+
legend: this.getLegendOptions(i),
|
|
2787
|
+
scales: this.getScalesOptions(e, l, c),
|
|
2788
|
+
axes: this.getAxesOptions(i, l, c),
|
|
2789
|
+
series: this.getSeriesOptions(e, i, o, a),
|
|
2790
|
+
plugins: [this.tooltipPlugin(i, o, l, c)]
|
|
2791
|
+
};
|
|
2792
|
+
}
|
|
2793
|
+
/**
|
|
2794
|
+
* Create tooltip plugin for hover values.
|
|
2795
|
+
* Shows a tooltip with formatted X and Y values when hovering over data points.
|
|
2796
|
+
* @param theme - Chart theme for styling
|
|
2797
|
+
* @param seriesNames - Names of the data series for the tooltip
|
|
2798
|
+
* @param xLabels - Optional category labels for categorical X axis
|
|
2799
|
+
* @param isXTemporal - Whether X axis contains temporal data
|
|
2800
|
+
* @returns uPlot plugin configuration
|
|
2801
|
+
*/
|
|
2802
|
+
tooltipPlugin(t, e, i, r) {
|
|
2803
|
+
let n = null, o;
|
|
2804
|
+
const a = () => {
|
|
2805
|
+
n && (n.style.display = "block");
|
|
2806
|
+
}, l = () => {
|
|
2807
|
+
n && (n.style.display = "none");
|
|
2808
|
+
}, c = (u) => {
|
|
2809
|
+
if (u == null) return "—";
|
|
2810
|
+
const p = Math.abs(u);
|
|
2811
|
+
return p >= 1e9 ? (u / 1e9).toFixed(2) + "B" : p >= 1e6 ? (u / 1e6).toFixed(2) + "M" : p >= 1e3 ? (u / 1e3).toFixed(2) + "K" : p < 1 && p > 0 ? u.toPrecision(3) : u.toLocaleString(void 0, { maximumFractionDigits: 2 });
|
|
2812
|
+
}, f = (u, p, g, y) => {
|
|
2813
|
+
const x = u.data[0][p];
|
|
2814
|
+
if (x == null) return "—";
|
|
2815
|
+
if (g && g.length > 0) {
|
|
2816
|
+
const A = Math.round(x);
|
|
2817
|
+
return g[A] ?? String(x);
|
|
2818
|
+
}
|
|
2819
|
+
return y ? new Date(x * 1e3).toLocaleDateString(void 0, {
|
|
2820
|
+
year: "numeric",
|
|
2821
|
+
month: "short",
|
|
2822
|
+
day: "numeric"
|
|
2823
|
+
}) : c(x);
|
|
2824
|
+
};
|
|
2825
|
+
return {
|
|
2826
|
+
hooks: {
|
|
2827
|
+
init: (u) => {
|
|
2828
|
+
o = u.over, n = document.createElement("div"), n.className = "uplot-tooltip", n.style.cssText = `
|
|
2829
|
+
position: absolute;
|
|
2830
|
+
display: none;
|
|
2831
|
+
background: ${t.tooltipBg};
|
|
2832
|
+
color: ${t.tooltipFg};
|
|
2833
|
+
padding: 8px 12px;
|
|
2834
|
+
border-radius: 4px;
|
|
2835
|
+
font-family: system-ui, sans-serif;
|
|
2836
|
+
font-size: 12px;
|
|
2837
|
+
pointer-events: none;
|
|
2838
|
+
z-index: 100;
|
|
2839
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
2840
|
+
white-space: nowrap;
|
|
2841
|
+
`, o.appendChild(n), o.addEventListener("mouseleave", l), o.addEventListener("mouseenter", a);
|
|
2842
|
+
},
|
|
2843
|
+
setCursor: (u) => {
|
|
2844
|
+
if (!n) return;
|
|
2845
|
+
const { left: p, top: g, idx: y } = u.cursor;
|
|
2846
|
+
if (p == null || g == null || p < 0 || y == null) {
|
|
2847
|
+
l();
|
|
2848
|
+
return;
|
|
2849
|
+
}
|
|
2850
|
+
a();
|
|
2851
|
+
let A = `<div style="margin-bottom: 6px; font-weight: 500;">${f(u, y, i, r)}</div>`;
|
|
2852
|
+
e.forEach((Mt, at) => {
|
|
2853
|
+
const Bt = at + 1, Dt = u.data[Bt]?.[y], Ut = t.seriesColors[at % t.seriesColors.length];
|
|
2854
|
+
A += `
|
|
2855
|
+
<div style="display: flex; align-items: center; gap: 8px; margin-top: 4px;">
|
|
2856
|
+
<span style="width: 10px; height: 10px; background: ${Ut}; border-radius: 2px;"></span>
|
|
2857
|
+
<span>${Mt}:</span>
|
|
2858
|
+
<span style="font-weight: 500;">${c(Dt)}</span>
|
|
2859
|
+
</div>
|
|
2860
|
+
`;
|
|
2861
|
+
}), n.innerHTML = A;
|
|
2862
|
+
const R = n.getBoundingClientRect(), Y = o.getBoundingClientRect();
|
|
2863
|
+
let ot = p + 15, N = g - R.height / 2;
|
|
2864
|
+
p + R.width + 20 > Y.width && (ot = p - R.width - 15), N < 0 && (N = 0), N + R.height > Y.height && (N = Y.height - R.height), n.style.left = ot + "px", n.style.top = N + "px";
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
};
|
|
2868
|
+
}
|
|
2869
|
+
/**
|
|
2870
|
+
* Get chart padding configuration.
|
|
2871
|
+
* @returns Object with padding array [top, right, bottom, left] in pixels
|
|
2872
|
+
*/
|
|
2873
|
+
getChartPadding() {
|
|
2874
|
+
return {
|
|
2875
|
+
padding: [16, 16, 16, 16]
|
|
2876
|
+
// top, right, bottom, left
|
|
2877
|
+
};
|
|
2878
|
+
}
|
|
2879
|
+
/**
|
|
2880
|
+
* Get cursor/crosshair options for hover interactions.
|
|
2881
|
+
* @param theme - Chart theme for styling
|
|
2882
|
+
* @returns uPlot cursor configuration
|
|
2883
|
+
*/
|
|
2884
|
+
getCursorOptions(t) {
|
|
2885
|
+
return {
|
|
2886
|
+
show: !0,
|
|
2887
|
+
drag: { x: !1, y: !1 },
|
|
2888
|
+
focus: { prox: 30 },
|
|
2889
|
+
points: {
|
|
2890
|
+
show: !0,
|
|
2891
|
+
size: 8,
|
|
2892
|
+
fill: t.background,
|
|
2893
|
+
stroke: t.foreground
|
|
2894
|
+
}
|
|
2895
|
+
};
|
|
2896
|
+
}
|
|
2897
|
+
/**
|
|
2898
|
+
* Get legend options (disabled, using custom legend instead).
|
|
2899
|
+
* @param _theme - Chart theme (unused, kept for consistency)
|
|
2900
|
+
* @returns uPlot legend configuration
|
|
2901
|
+
*/
|
|
2902
|
+
getLegendOptions(t) {
|
|
2903
|
+
return {
|
|
2904
|
+
show: !1
|
|
2905
|
+
// We'll create a custom inline legend
|
|
2906
|
+
};
|
|
2907
|
+
}
|
|
2908
|
+
/**
|
|
2909
|
+
* Create a custom legend below the chart area.
|
|
2910
|
+
* Displays series names with color swatches.
|
|
2911
|
+
* @param seriesNames - Names of the data series
|
|
2912
|
+
* @param theme - Chart theme for styling
|
|
2913
|
+
*/
|
|
2914
|
+
createInlineLegend(t, e) {
|
|
2915
|
+
const i = this.container.querySelector(".uplot-legend-below");
|
|
2916
|
+
i && i.remove();
|
|
2917
|
+
const r = document.createElement("div");
|
|
2918
|
+
r.className = "uplot-legend-below", r.style.cssText = `
|
|
2919
|
+
display: flex;
|
|
2920
|
+
justify-content: center;
|
|
2921
|
+
gap: 24px;
|
|
2922
|
+
font-family: system-ui, sans-serif;
|
|
2923
|
+
font-size: 12px;
|
|
2924
|
+
padding: 12px 0 0 0;
|
|
2925
|
+
flex-shrink: 0;
|
|
2926
|
+
`, t.forEach((n, o) => {
|
|
2927
|
+
const a = e.seriesColors[o % e.seriesColors.length], l = document.createElement("div");
|
|
2928
|
+
l.style.cssText = `
|
|
2929
|
+
display: flex;
|
|
2930
|
+
align-items: center;
|
|
2931
|
+
gap: 6px;
|
|
2932
|
+
color: ${e.foreground};
|
|
2933
|
+
`;
|
|
2934
|
+
const c = document.createElement("span");
|
|
2935
|
+
c.style.cssText = `
|
|
2936
|
+
width: 16px;
|
|
2937
|
+
height: 3px;
|
|
2938
|
+
background: ${a};
|
|
2939
|
+
border-radius: 2px;
|
|
2940
|
+
`;
|
|
2941
|
+
const f = document.createElement("span");
|
|
2942
|
+
f.textContent = n, l.appendChild(c), l.appendChild(f), r.appendChild(l);
|
|
2943
|
+
}), this.container.appendChild(r);
|
|
2944
|
+
}
|
|
2945
|
+
/**
|
|
2946
|
+
* Get scales configuration for X and Y axes.
|
|
2947
|
+
* Configures time mode for temporal data and range for categorical data.
|
|
2948
|
+
* @param _type - Chart type (unused, kept for future use)
|
|
2949
|
+
* @param xLabels - Optional category labels for categorical X axis
|
|
2950
|
+
* @param isXTemporal - Whether X axis contains temporal data
|
|
2951
|
+
* @returns uPlot scales configuration
|
|
2952
|
+
*/
|
|
2953
|
+
getScalesOptions(t, e, i) {
|
|
2954
|
+
const r = {
|
|
2955
|
+
x: {
|
|
2956
|
+
// Only use time mode for actual temporal data, not for generic numeric data
|
|
2957
|
+
time: i === !0
|
|
2958
|
+
},
|
|
2959
|
+
y: {
|
|
2960
|
+
auto: !0,
|
|
2961
|
+
// For bar/histogram charts, ensure y-axis starts at 0
|
|
2962
|
+
range: (n, o, a) => {
|
|
2963
|
+
const l = Math.min(0, o), c = a * 1.1;
|
|
2964
|
+
return [l, c];
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
};
|
|
2968
|
+
return e && e.length > 0 && (r.x.range = () => [-0.5, e.length - 0.5]), r;
|
|
2969
|
+
}
|
|
2970
|
+
/**
|
|
2971
|
+
* Get axes configuration for X and Y axes.
|
|
2972
|
+
* Configures styling, grid, ticks, and value formatters.
|
|
2973
|
+
* @param theme - Chart theme for styling
|
|
2974
|
+
* @param xLabels - Optional category labels for categorical X axis
|
|
2975
|
+
* @param _isXTemporal - Whether X axis is temporal (unused, handled by uPlot)
|
|
2976
|
+
* @returns Array of uPlot axis configurations [xAxis, yAxis]
|
|
2977
|
+
*/
|
|
2978
|
+
getAxesOptions(t, e, i) {
|
|
2979
|
+
const r = {
|
|
2980
|
+
stroke: t.axisColor,
|
|
2981
|
+
grid: {
|
|
2982
|
+
stroke: t.gridColor,
|
|
2983
|
+
width: 1
|
|
2984
|
+
},
|
|
2985
|
+
ticks: {
|
|
2986
|
+
stroke: t.gridColor,
|
|
2987
|
+
width: 1
|
|
2988
|
+
},
|
|
2989
|
+
font: "12px system-ui, sans-serif",
|
|
2990
|
+
labelFont: "12px system-ui, sans-serif"
|
|
2991
|
+
};
|
|
2992
|
+
e && e.length > 0 && (r.splits = () => e.map((o, a) => a), r.values = (o, a) => a.map((l) => {
|
|
2993
|
+
const c = Math.round(l);
|
|
2994
|
+
return c >= 0 && c < e.length ? e[c] : "";
|
|
2995
|
+
}));
|
|
2996
|
+
const n = {
|
|
2997
|
+
stroke: t.axisColor,
|
|
2998
|
+
grid: {
|
|
2999
|
+
stroke: t.gridColor,
|
|
3000
|
+
width: 1
|
|
3001
|
+
},
|
|
3002
|
+
ticks: {
|
|
3003
|
+
stroke: t.gridColor,
|
|
3004
|
+
width: 1
|
|
3005
|
+
},
|
|
3006
|
+
font: "12px system-ui, sans-serif",
|
|
3007
|
+
labelFont: "12px system-ui, sans-serif",
|
|
3008
|
+
// Dynamic size calculation based on formatted label width
|
|
3009
|
+
size: (o, a, l, c) => {
|
|
3010
|
+
if (c === 0) return o.axes[l].size;
|
|
3011
|
+
const f = Math.max(...a.map((u) => String(u).length));
|
|
3012
|
+
return Math.max(40, f * 8 + 16);
|
|
3013
|
+
},
|
|
3014
|
+
// Format large numbers with K/M/B suffixes
|
|
3015
|
+
values: (o, a) => a.map((l) => this.formatAxisValue(l))
|
|
3016
|
+
};
|
|
3017
|
+
return [r, n];
|
|
3018
|
+
}
|
|
3019
|
+
/**
|
|
3020
|
+
* Format axis value with K/M/B suffixes for large numbers.
|
|
3021
|
+
* @param value - The numeric value to format
|
|
3022
|
+
* @returns Formatted string with appropriate suffix (K, M, B) or decimal notation
|
|
3023
|
+
*/
|
|
3024
|
+
formatAxisValue(t) {
|
|
3025
|
+
const e = Math.abs(t);
|
|
3026
|
+
return e >= 1e9 ? (t / 1e9).toFixed(1).replace(/\.0$/, "") + "B" : e >= 1e6 ? (t / 1e6).toFixed(1).replace(/\.0$/, "") + "M" : e >= 1e3 ? (t / 1e3).toFixed(1).replace(/\.0$/, "") + "K" : e === 0 ? "0" : e < 1 ? t.toPrecision(2) : t.toFixed(0);
|
|
3027
|
+
}
|
|
3028
|
+
/**
|
|
3029
|
+
* Get series configuration based on chart type.
|
|
3030
|
+
* Creates series configs for each Y column with appropriate styling.
|
|
3031
|
+
* @param type - The chart type (line, bar, scatter, histogram)
|
|
3032
|
+
* @param theme - Chart theme for colors
|
|
3033
|
+
* @param seriesNames - Names of the Y series
|
|
3034
|
+
* @param _axes - Axis selection (unused, kept for future use)
|
|
3035
|
+
* @returns Array of uPlot series configurations
|
|
3036
|
+
*/
|
|
3037
|
+
getSeriesOptions(t, e, i, r) {
|
|
3038
|
+
const n = [{}];
|
|
3039
|
+
return i.forEach((o, a) => {
|
|
3040
|
+
const l = e.seriesColors[a % e.seriesColors.length], c = this.getSeriesConfig(t, o, l, e);
|
|
3041
|
+
n.push(c);
|
|
3042
|
+
}), n;
|
|
3043
|
+
}
|
|
3044
|
+
/**
|
|
3045
|
+
* Get configuration for a single series based on chart type.
|
|
3046
|
+
* @param type - The chart type determining visual style
|
|
3047
|
+
* @param name - Series name for the label
|
|
3048
|
+
* @param color - Series color
|
|
3049
|
+
* @param _theme - Chart theme (unused, kept for future use)
|
|
3050
|
+
* @returns uPlot series configuration
|
|
3051
|
+
*/
|
|
3052
|
+
getSeriesConfig(t, e, i, r) {
|
|
3053
|
+
const n = {
|
|
3054
|
+
label: e,
|
|
3055
|
+
stroke: i,
|
|
3056
|
+
width: 2
|
|
3057
|
+
};
|
|
3058
|
+
switch (t) {
|
|
3059
|
+
case "line":
|
|
3060
|
+
return {
|
|
3061
|
+
...n,
|
|
3062
|
+
fill: this.addAlpha(i, 0.1),
|
|
3063
|
+
points: { show: !0, size: 4 }
|
|
3064
|
+
};
|
|
3065
|
+
case "bar":
|
|
3066
|
+
case "histogram":
|
|
3067
|
+
return {
|
|
3068
|
+
...n,
|
|
3069
|
+
fill: this.addAlpha(i, 0.7),
|
|
3070
|
+
paths: this.barPathBuilder(),
|
|
3071
|
+
points: { show: !1 }
|
|
3072
|
+
};
|
|
3073
|
+
case "scatter":
|
|
3074
|
+
return {
|
|
3075
|
+
...n,
|
|
3076
|
+
fill: i,
|
|
3077
|
+
paths: () => null,
|
|
3078
|
+
// No line connecting points
|
|
3079
|
+
points: {
|
|
3080
|
+
show: !0,
|
|
3081
|
+
size: 8,
|
|
3082
|
+
fill: i
|
|
3083
|
+
}
|
|
3084
|
+
};
|
|
3085
|
+
default:
|
|
3086
|
+
return n;
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
/**
|
|
3090
|
+
* Create bar chart path builder.
|
|
3091
|
+
* Returns a function that draws rectangular bars for each data point.
|
|
3092
|
+
* @returns uPlot path builder function for bar charts
|
|
3093
|
+
*/
|
|
3094
|
+
barPathBuilder() {
|
|
3095
|
+
return (t, e, i, r) => {
|
|
3096
|
+
const n = t.data[0], o = t.data[e], a = this.calculateBarWidth(t, n.length);
|
|
3097
|
+
let l = new Path2D();
|
|
3098
|
+
for (let c = i; c <= r; c++) {
|
|
3099
|
+
const f = t.valToPos(n[c], "x", !0), u = t.valToPos(o[c], "y", !0), p = t.valToPos(0, "y", !0), g = f - a / 2, y = p - u;
|
|
3100
|
+
l.rect(g, u, a, y);
|
|
3101
|
+
}
|
|
3102
|
+
return {
|
|
3103
|
+
stroke: l,
|
|
3104
|
+
fill: l
|
|
3105
|
+
};
|
|
3106
|
+
};
|
|
3107
|
+
}
|
|
3108
|
+
/**
|
|
3109
|
+
* Calculate appropriate bar width based on data density.
|
|
3110
|
+
* Ensures bars don't overlap and maintain minimum/maximum widths.
|
|
3111
|
+
* @param u - The uPlot instance
|
|
3112
|
+
* @param dataLength - Number of data points
|
|
3113
|
+
* @returns Bar width in pixels
|
|
3114
|
+
*/
|
|
3115
|
+
calculateBarWidth(t, e) {
|
|
3116
|
+
const i = t.bbox.width, r = 60, n = 4, l = i / e * (1 - 0.2);
|
|
3117
|
+
return Math.max(n, Math.min(r, l));
|
|
3118
|
+
}
|
|
3119
|
+
/**
|
|
3120
|
+
* Add alpha channel to a color.
|
|
3121
|
+
* Converts hex or rgb colors to rgba format.
|
|
3122
|
+
* @param color - The color string (hex or rgb format)
|
|
3123
|
+
* @param alpha - Alpha value between 0 and 1
|
|
3124
|
+
* @returns rgba color string
|
|
3125
|
+
*/
|
|
3126
|
+
addAlpha(t, e) {
|
|
3127
|
+
if (t.startsWith("#")) {
|
|
3128
|
+
const r = t.slice(1);
|
|
3129
|
+
let n, o, a;
|
|
3130
|
+
return r.length === 3 ? (n = parseInt(r[0] + r[0], 16), o = parseInt(r[1] + r[1], 16), a = parseInt(r[2] + r[2], 16)) : (n = parseInt(r.slice(0, 2), 16), o = parseInt(r.slice(2, 4), 16), a = parseInt(r.slice(4, 6), 16)), `rgba(${n}, ${o}, ${a}, ${e})`;
|
|
3131
|
+
}
|
|
3132
|
+
const i = t.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
3133
|
+
return i ? `rgba(${i[1]}, ${i[2]}, ${i[3]}, ${e})` : t;
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
const ft = ["line", "bar", "scatter", "histogram"];
|
|
3137
|
+
function oi(s) {
|
|
3138
|
+
const t = s.replace(/^\.chart\s*/, "").trim();
|
|
3139
|
+
if (!t)
|
|
3140
|
+
return {
|
|
3141
|
+
success: !0,
|
|
3142
|
+
options: { action: "show" }
|
|
3143
|
+
};
|
|
3144
|
+
const e = t.toLowerCase();
|
|
3145
|
+
if (e === "export" || e === "export png")
|
|
3146
|
+
return {
|
|
3147
|
+
success: !0,
|
|
3148
|
+
options: { action: "export", exportFormat: "png" }
|
|
3149
|
+
};
|
|
3150
|
+
const i = { action: "show" }, r = ai(t);
|
|
3151
|
+
for (const n of r) {
|
|
3152
|
+
const [o, a] = li(n);
|
|
3153
|
+
if (!o || !a)
|
|
3154
|
+
return {
|
|
3155
|
+
success: !1,
|
|
3156
|
+
error: `Invalid argument: '${n}'. Use format: key=value`
|
|
3157
|
+
};
|
|
3158
|
+
switch (o.toLowerCase()) {
|
|
3159
|
+
case "type":
|
|
3160
|
+
const l = a.toLowerCase();
|
|
3161
|
+
if (!ft.includes(l))
|
|
3162
|
+
return {
|
|
3163
|
+
success: !1,
|
|
3164
|
+
error: `Unknown chart type '${a}'. Use: ${ft.join(", ")}`
|
|
3165
|
+
};
|
|
3166
|
+
i.type = l;
|
|
3167
|
+
break;
|
|
3168
|
+
case "x":
|
|
3169
|
+
i.x = a;
|
|
3170
|
+
break;
|
|
3171
|
+
case "y":
|
|
3172
|
+
i.y = a.split(",").map((c) => c.trim());
|
|
3173
|
+
break;
|
|
3174
|
+
default:
|
|
3175
|
+
return {
|
|
3176
|
+
success: !1,
|
|
3177
|
+
error: `Unknown option '${o}'. Valid options: type, x, y`
|
|
3178
|
+
};
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
return {
|
|
3182
|
+
success: !0,
|
|
3183
|
+
options: i
|
|
3184
|
+
};
|
|
3185
|
+
}
|
|
3186
|
+
function ai(s) {
|
|
3187
|
+
const t = [];
|
|
3188
|
+
let e = "", i = !1, r = "";
|
|
3189
|
+
for (let n = 0; n < s.length; n++) {
|
|
3190
|
+
const o = s[n];
|
|
3191
|
+
(o === '"' || o === "'") && !i ? (i = !0, r = o) : o === r && i ? (i = !1, r = "") : o === " " && !i ? e && (t.push(e), e = "") : e += o;
|
|
3192
|
+
}
|
|
3193
|
+
return e && t.push(e), t;
|
|
3194
|
+
}
|
|
3195
|
+
function li(s) {
|
|
3196
|
+
const t = s.indexOf("=");
|
|
3197
|
+
if (t === -1)
|
|
3198
|
+
return [null, null];
|
|
3199
|
+
const e = s.slice(0, t).trim(), i = s.slice(t + 1).trim();
|
|
3200
|
+
return !e || !i ? [null, null] : [e, i];
|
|
3201
|
+
}
|
|
3202
|
+
function ci() {
|
|
3203
|
+
return `Usage: .chart [options]
|
|
3204
|
+
|
|
3205
|
+
Show an interactive chart of the last query result.
|
|
3206
|
+
|
|
3207
|
+
Actions:
|
|
3208
|
+
.chart Show chart with auto-detection
|
|
3209
|
+
.chart export Export chart as PNG
|
|
3210
|
+
|
|
3211
|
+
Options:
|
|
3212
|
+
type=TYPE Chart type: line, bar, scatter, histogram
|
|
3213
|
+
x=COLUMN Column for X axis
|
|
3214
|
+
y=COLUMN[,...] Column(s) for Y axis (comma-separated)
|
|
3215
|
+
|
|
3216
|
+
Examples:
|
|
3217
|
+
.chart Auto-detect chart type
|
|
3218
|
+
.chart type=bar Force bar chart
|
|
3219
|
+
.chart x=date y=revenue Specify axes
|
|
3220
|
+
.chart type=line y=revenue,cost Multiple series
|
|
3221
|
+
|
|
3222
|
+
Press ESC to close the chart, Ctrl+S to export as PNG.`;
|
|
3223
|
+
}
|
|
3224
|
+
function hi(s, t) {
|
|
3225
|
+
const e = s.ctx?.canvas;
|
|
3226
|
+
if (!e)
|
|
3227
|
+
throw new Error("Chart canvas not available");
|
|
3228
|
+
const r = ui(), n = e.toDataURL("image/png"), o = document.createElement("a");
|
|
3229
|
+
o.download = r, o.href = n, document.body.appendChild(o), o.click(), document.body.removeChild(o);
|
|
3230
|
+
}
|
|
3231
|
+
function ui() {
|
|
3232
|
+
const s = /* @__PURE__ */ new Date(), t = s.getFullYear(), e = String(s.getMonth() + 1).padStart(2, "0"), i = String(s.getDate()).padStart(2, "0"), r = String(s.getHours()).padStart(2, "0"), n = String(s.getMinutes()).padStart(2, "0"), o = String(s.getSeconds()).padStart(2, "0");
|
|
3233
|
+
return `duckdb-chart-${t}-${e}-${i}-${r}${n}${o}.png`;
|
|
3234
|
+
}
|
|
3235
|
+
class di {
|
|
3236
|
+
/**
|
|
3237
|
+
* Creates a new ChartManager instance.
|
|
3238
|
+
* @param container - The HTML element that contains the terminal (overlay parent)
|
|
3239
|
+
* @param options - Configuration options
|
|
3240
|
+
* @param options.enabled - Whether charts feature is enabled (default: false)
|
|
3241
|
+
* @param options.themeMode - Initial theme mode ('dark' or 'light')
|
|
3242
|
+
* @param options.themeColors - Custom theme colors from terminal theme
|
|
3243
|
+
*/
|
|
3244
|
+
constructor(t, e = {}) {
|
|
3245
|
+
h(this, "container");
|
|
3246
|
+
h(this, "overlay", null);
|
|
3247
|
+
h(this, "renderer", null);
|
|
3248
|
+
h(this, "uPlot", null);
|
|
3249
|
+
h(this, "enabled");
|
|
3250
|
+
h(this, "themeMode");
|
|
3251
|
+
h(this, "themeColors", null);
|
|
3252
|
+
this.container = t, this.enabled = e.enabled ?? !1, this.themeMode = e.themeMode ?? "dark", this.themeColors = e.themeColors ?? null;
|
|
3253
|
+
}
|
|
3254
|
+
/**
|
|
3255
|
+
* Check if charts feature is enabled.
|
|
3256
|
+
* @returns True if charts are enabled in configuration
|
|
3257
|
+
*/
|
|
3258
|
+
isEnabled() {
|
|
3259
|
+
return this.enabled;
|
|
3260
|
+
}
|
|
3261
|
+
/**
|
|
3262
|
+
* Update theme settings for chart rendering.
|
|
3263
|
+
* @param mode - Theme mode ('dark' or 'light')
|
|
3264
|
+
* @param colors - Optional custom theme colors
|
|
3265
|
+
*/
|
|
3266
|
+
setTheme(t, e) {
|
|
3267
|
+
this.themeMode = t, e && (this.themeColors = e);
|
|
3268
|
+
}
|
|
3269
|
+
/**
|
|
3270
|
+
* Execute a .chart command.
|
|
3271
|
+
* Parses the command and performs the appropriate action (show/export).
|
|
3272
|
+
* @param input - The full command string (e.g., ".chart type=bar")
|
|
3273
|
+
* @param lastResult - The last query result to visualize
|
|
3274
|
+
* @returns Result object with success status and message/error
|
|
3275
|
+
*/
|
|
3276
|
+
async executeCommand(t, e) {
|
|
3277
|
+
if (!this.enabled)
|
|
3278
|
+
return {
|
|
3279
|
+
success: !1,
|
|
3280
|
+
error: "Charts feature is disabled. Enable with enableCharts: true in config."
|
|
3281
|
+
};
|
|
3282
|
+
const i = oi(t);
|
|
3283
|
+
if (!i.success || !i.options)
|
|
3284
|
+
return {
|
|
3285
|
+
success: !1,
|
|
3286
|
+
error: i.error ?? "Failed to parse chart command"
|
|
3287
|
+
};
|
|
3288
|
+
const r = i.options;
|
|
3289
|
+
switch (r.action) {
|
|
3290
|
+
case "export":
|
|
3291
|
+
return this.exportChart();
|
|
3292
|
+
case "show":
|
|
3293
|
+
return this.showChart(e, r);
|
|
3294
|
+
default:
|
|
3295
|
+
return {
|
|
3296
|
+
success: !1,
|
|
3297
|
+
error: `Unknown action: ${r.action}`
|
|
3298
|
+
};
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
/**
|
|
3302
|
+
* Show chart with the given data and options.
|
|
3303
|
+
* Handles overlay creation, uPlot loading, data transformation, and rendering.
|
|
3304
|
+
* @param result - The query result to visualize
|
|
3305
|
+
* @param options - Parsed chart command options
|
|
3306
|
+
* @returns Result object with success status and message/error
|
|
3307
|
+
*/
|
|
3308
|
+
async showChart(t, e) {
|
|
3309
|
+
const i = Je(t);
|
|
3310
|
+
if (!i.valid)
|
|
3311
|
+
return {
|
|
3312
|
+
success: !1,
|
|
3313
|
+
error: i.error
|
|
3314
|
+
};
|
|
3315
|
+
if (e.x || e.y) {
|
|
3316
|
+
const g = [];
|
|
3317
|
+
e.x && g.push(e.x), e.y && g.push(...e.y);
|
|
3318
|
+
const { columns: y } = ht(t), x = Ge(g, y);
|
|
3319
|
+
if (!x.valid)
|
|
3320
|
+
return {
|
|
3321
|
+
success: !1,
|
|
3322
|
+
error: `Column${x.missing.length > 1 ? "s" : ""} '${x.missing.join("', '")}' not found in result.`
|
|
3323
|
+
};
|
|
3324
|
+
}
|
|
3325
|
+
this.overlay || (this.overlay = new Oe(this.container, {
|
|
3326
|
+
onDismiss: () => this.handleDismiss(),
|
|
3327
|
+
onExport: () => this.handleExport()
|
|
3328
|
+
})), await this.overlay.show();
|
|
3329
|
+
const r = this.overlay.getChartContainer();
|
|
3330
|
+
if (!r)
|
|
3331
|
+
return {
|
|
3332
|
+
success: !1,
|
|
3333
|
+
error: "Failed to create chart container"
|
|
3334
|
+
};
|
|
3335
|
+
const n = !this.uPlot;
|
|
3336
|
+
n && this.overlay.showLoading();
|
|
3337
|
+
try {
|
|
3338
|
+
this.uPlot || (this.uPlot = await Ue.load());
|
|
3339
|
+
} catch {
|
|
3340
|
+
return this.overlay.hideLoading(), await this.overlay.hide(), {
|
|
3341
|
+
success: !1,
|
|
3342
|
+
error: "Failed to load charts library. Check your network connection."
|
|
3343
|
+
};
|
|
3344
|
+
}
|
|
3345
|
+
n && this.overlay.hideLoading();
|
|
3346
|
+
const o = ht(t, {
|
|
3347
|
+
type: e.type,
|
|
3348
|
+
x: e.x,
|
|
3349
|
+
y: e.y,
|
|
3350
|
+
duckdbTypes: t.columnTypes
|
|
3351
|
+
}), a = this.themeColors ? Ze(this.themeColors) : ri(this.themeMode);
|
|
3352
|
+
r.style.setProperty("--chart-bg", a.background), r.style.background = a.background, r.style.display = "flex", r.style.flexDirection = "column";
|
|
3353
|
+
const l = this.overlay.getChartDimensions(), c = 40, f = 36, u = l.width - c, p = l.height - c - f;
|
|
3354
|
+
return this.renderer && this.renderer.destroy(), this.renderer = new ni(this.uPlot, r), this.renderer.render(o.data, {
|
|
3355
|
+
type: o.chartType,
|
|
3356
|
+
theme: a,
|
|
3357
|
+
width: u,
|
|
3358
|
+
height: p,
|
|
3359
|
+
seriesNames: o.seriesNames,
|
|
3360
|
+
axes: o.axes,
|
|
3361
|
+
xLabels: o.axes.xLabels,
|
|
3362
|
+
isXTemporal: o.isXTemporal
|
|
3363
|
+
}), {
|
|
3364
|
+
success: !0,
|
|
3365
|
+
message: `Showing ${o.chartType} chart with ${o.seriesNames.length} series`
|
|
3366
|
+
};
|
|
3367
|
+
}
|
|
3368
|
+
/**
|
|
3369
|
+
* Export current chart as PNG.
|
|
3370
|
+
* Downloads the chart as a PNG file.
|
|
3371
|
+
* @returns Result object with success status and message/error
|
|
3372
|
+
*/
|
|
3373
|
+
exportChart() {
|
|
3374
|
+
const t = this.renderer?.getChart();
|
|
3375
|
+
if (!t)
|
|
3376
|
+
return {
|
|
3377
|
+
success: !1,
|
|
3378
|
+
error: "No chart to export. Show a chart first."
|
|
3379
|
+
};
|
|
3380
|
+
try {
|
|
3381
|
+
return hi(t), {
|
|
3382
|
+
success: !0,
|
|
3383
|
+
message: "Chart exported as PNG"
|
|
3384
|
+
};
|
|
3385
|
+
} catch {
|
|
3386
|
+
return {
|
|
3387
|
+
success: !1,
|
|
3388
|
+
error: "Failed to export chart"
|
|
3389
|
+
};
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
/**
|
|
3393
|
+
* Handle overlay dismiss event (ESC key or click outside).
|
|
3394
|
+
* Called by the overlay when user dismisses the chart.
|
|
3395
|
+
*/
|
|
3396
|
+
handleDismiss() {
|
|
3397
|
+
}
|
|
3398
|
+
/**
|
|
3399
|
+
* Handle export shortcut (Ctrl+S) from overlay.
|
|
3400
|
+
* Triggers PNG export of the current chart.
|
|
3401
|
+
*/
|
|
3402
|
+
handleExport() {
|
|
3403
|
+
this.exportChart();
|
|
3404
|
+
}
|
|
3405
|
+
/**
|
|
3406
|
+
* Get help text for .chart command.
|
|
3407
|
+
* @returns Formatted help text string
|
|
3408
|
+
*/
|
|
3409
|
+
getHelpText() {
|
|
3410
|
+
return ci();
|
|
3411
|
+
}
|
|
3412
|
+
/**
|
|
3413
|
+
* Destroy the chart manager and clean up all resources.
|
|
3414
|
+
* Should be called when the terminal is destroyed.
|
|
3415
|
+
*/
|
|
3416
|
+
destroy() {
|
|
3417
|
+
this.renderer?.destroy(), this.overlay?.destroy(), this.renderer = null, this.overlay = null;
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
const $t = "v1", At = 2e3, G = (s) => btoa(unescape(encodeURIComponent(s))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""), fi = (s) => {
|
|
3421
|
+
let t = s;
|
|
3422
|
+
for (; t.length % 4; )
|
|
3423
|
+
t += "=";
|
|
3424
|
+
return t = t.replace(/-/g, "+").replace(/_/g, "/"), decodeURIComponent(escape(atob(t)));
|
|
3425
|
+
}, mt = () => {
|
|
3426
|
+
const { protocol: s, hostname: t, port: e } = window.location, i = e ? `:${e}` : "";
|
|
3427
|
+
return `${s}//${t}${i}/#$queries=${$t}`;
|
|
3428
|
+
}, Gi = (s) => G(s).length + 1, q = (s) => {
|
|
3429
|
+
if (s.length === 0) {
|
|
3430
|
+
const r = mt();
|
|
3431
|
+
return {
|
|
3432
|
+
url: r,
|
|
3433
|
+
characterCount: r.length,
|
|
3434
|
+
queryCount: 0
|
|
3435
|
+
};
|
|
3436
|
+
}
|
|
3437
|
+
const t = mt(), e = s.map(G), i = `${t},${e.join(",")}`;
|
|
3438
|
+
return {
|
|
3439
|
+
url: i,
|
|
3440
|
+
characterCount: i.length,
|
|
3441
|
+
queryCount: s.length
|
|
3442
|
+
};
|
|
3443
|
+
}, mi = (s) => q(s).characterCount, Vi = (s, t, e = At) => {
|
|
3444
|
+
const i = [...s, t];
|
|
3445
|
+
return mi(i) > e;
|
|
3446
|
+
}, Yi = () => {
|
|
3447
|
+
const s = window.location.hash;
|
|
3448
|
+
if (!s.startsWith("#$queries="))
|
|
3449
|
+
return null;
|
|
3450
|
+
const e = s.slice(10).split(",");
|
|
3451
|
+
if (e.length < 1)
|
|
3452
|
+
return null;
|
|
3453
|
+
const i = e[0];
|
|
3454
|
+
if (i !== $t)
|
|
3455
|
+
return console.warn(`Unsupported sharing URL version: ${i}`), null;
|
|
3456
|
+
const r = e.slice(1);
|
|
3457
|
+
if (r.length === 0)
|
|
3458
|
+
return null;
|
|
3459
|
+
try {
|
|
3460
|
+
return r.map(fi);
|
|
3461
|
+
} catch (n) {
|
|
3462
|
+
return console.error("Failed to decode shared queries:", n), null;
|
|
3463
|
+
}
|
|
3464
|
+
}, Ki = () => {
|
|
3465
|
+
const { protocol: s, hostname: t, port: e, pathname: i } = window.location, r = e ? `:${e}` : "", n = `${s}//${t}${r}${i}`;
|
|
3466
|
+
history.replaceState(null, "", n);
|
|
3467
|
+
}, pi = {
|
|
3468
|
+
maxUrlLength: At,
|
|
3469
|
+
initialQueryCount: 5,
|
|
3470
|
+
loadMoreCount: 5
|
|
3471
|
+
};
|
|
3472
|
+
class gi {
|
|
3473
|
+
/**
|
|
3474
|
+
* Creates a new SharingModal instance.
|
|
3475
|
+
* @param container - The parent HTML element to attach the modal to
|
|
3476
|
+
* @param events - Event handlers for modal interactions
|
|
3477
|
+
* @param config - Configuration options
|
|
3478
|
+
*/
|
|
3479
|
+
constructor(t, e = {}, i = {}) {
|
|
3480
|
+
h(this, "container");
|
|
3481
|
+
h(this, "overlay", null);
|
|
3482
|
+
h(this, "modalContainer", null);
|
|
3483
|
+
h(this, "queryListContainer", null);
|
|
3484
|
+
h(this, "footerContainer", null);
|
|
3485
|
+
h(this, "state", "hidden");
|
|
3486
|
+
h(this, "events");
|
|
3487
|
+
h(this, "config");
|
|
3488
|
+
// Query state
|
|
3489
|
+
h(this, "allQueries", []);
|
|
3490
|
+
h(this, "displayedQueries", []);
|
|
3491
|
+
h(this, "selectedIndices", /* @__PURE__ */ new Set());
|
|
3492
|
+
h(this, "focusedIndex", -1);
|
|
3493
|
+
h(this, "displayedCount", 0);
|
|
3494
|
+
// Event handlers
|
|
3495
|
+
h(this, "keyHandler", null);
|
|
3496
|
+
h(this, "clickHandler", null);
|
|
3497
|
+
/** Transition duration in milliseconds */
|
|
3498
|
+
h(this, "TRANSITION_DURATION", 200);
|
|
3499
|
+
this.container = t, this.events = e, this.config = { ...pi, ...i };
|
|
3500
|
+
}
|
|
3501
|
+
/**
|
|
3502
|
+
* Get current modal state.
|
|
3503
|
+
*/
|
|
3504
|
+
getState() {
|
|
3505
|
+
return this.state;
|
|
3506
|
+
}
|
|
3507
|
+
/**
|
|
3508
|
+
* Check if modal is currently visible or in the process of showing.
|
|
3509
|
+
*/
|
|
3510
|
+
isVisible() {
|
|
3511
|
+
return this.state === "visible" || this.state === "showing";
|
|
3512
|
+
}
|
|
3513
|
+
/**
|
|
3514
|
+
* Show the modal with the given queries.
|
|
3515
|
+
* @param queries - Array of SQL queries from history (oldest first)
|
|
3516
|
+
*/
|
|
3517
|
+
async show(t) {
|
|
3518
|
+
if (!(this.state === "visible" || this.state === "showing"))
|
|
3519
|
+
return this.allQueries = t.filter((e) => !e.trim().startsWith(".")).reverse(), this.selectedIndices.clear(), this.displayedCount = 0, this.focusedIndex = -1, this.state = "showing", this.createOverlay(), this.loadInitialQueries(), this.setupKeyboardHandler(), this.updateFooter(), new Promise((e) => {
|
|
3520
|
+
this.overlay?.offsetHeight, this.overlay && (this.overlay.style.opacity = "1"), setTimeout(() => {
|
|
3521
|
+
this.state = "visible", this.displayedQueries.length > 0 && (this.focusedIndex = 0, this.updateFocusedItem()), e();
|
|
3522
|
+
}, this.TRANSITION_DURATION);
|
|
3523
|
+
});
|
|
3524
|
+
}
|
|
3525
|
+
/**
|
|
3526
|
+
* Hide the modal with fade-out animation.
|
|
3527
|
+
*/
|
|
3528
|
+
async hide() {
|
|
3529
|
+
if (!(this.state === "hidden" || this.state === "hiding"))
|
|
3530
|
+
return this.state = "hiding", this.overlay && (this.overlay.style.opacity = "0"), new Promise((t) => {
|
|
3531
|
+
setTimeout(() => {
|
|
3532
|
+
this.destroyOverlay(), this.state = "hidden", t();
|
|
3533
|
+
}, this.TRANSITION_DURATION);
|
|
3534
|
+
});
|
|
3535
|
+
}
|
|
3536
|
+
/**
|
|
3537
|
+
* Clean up all resources.
|
|
3538
|
+
*/
|
|
3539
|
+
destroy() {
|
|
3540
|
+
this.destroyOverlay(), this.state = "hidden";
|
|
3541
|
+
}
|
|
3542
|
+
/**
|
|
3543
|
+
* Get the currently selected queries in execution order.
|
|
3544
|
+
*/
|
|
3545
|
+
getSelectedQueries() {
|
|
3546
|
+
const t = [];
|
|
3547
|
+
for (const e of this.selectedIndices) {
|
|
3548
|
+
const i = this.displayedQueries[e];
|
|
3549
|
+
if (i) {
|
|
3550
|
+
const r = this.allQueries.length - 1 - e;
|
|
3551
|
+
t.push({ index: r, query: i.query });
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
return t.sort((e, i) => e.index - i.index), t.map((e) => e.query);
|
|
3555
|
+
}
|
|
3556
|
+
/**
|
|
3557
|
+
* Calculate current URL length for selected queries.
|
|
3558
|
+
*/
|
|
3559
|
+
getCurrentURLLength() {
|
|
3560
|
+
const t = this.getSelectedQueries();
|
|
3561
|
+
return q(t).characterCount;
|
|
3562
|
+
}
|
|
3563
|
+
/**
|
|
3564
|
+
* Check if adding a query would exceed the limit.
|
|
3565
|
+
*/
|
|
3566
|
+
wouldExceedLimit(t) {
|
|
3567
|
+
const e = this.getSelectedQueries();
|
|
3568
|
+
return q([...e, t]).characterCount > this.config.maxUrlLength;
|
|
3569
|
+
}
|
|
3570
|
+
/**
|
|
3571
|
+
* Load initial batch of queries.
|
|
3572
|
+
*/
|
|
3573
|
+
loadInitialQueries() {
|
|
3574
|
+
this.displayedQueries = [], this.displayedCount = Math.min(this.config.initialQueryCount, this.allQueries.length);
|
|
3575
|
+
for (let t = 0; t < this.displayedCount; t++) {
|
|
3576
|
+
const e = this.allQueries[t];
|
|
3577
|
+
this.displayedQueries.push({
|
|
3578
|
+
query: e,
|
|
3579
|
+
order: this.allQueries.length - t,
|
|
3580
|
+
// Order shows position from oldest
|
|
3581
|
+
selected: !1,
|
|
3582
|
+
encodedLength: G(e).length + 1
|
|
3583
|
+
});
|
|
3584
|
+
}
|
|
3585
|
+
this.renderQueryList();
|
|
3586
|
+
}
|
|
3587
|
+
/**
|
|
3588
|
+
* Load more older queries.
|
|
3589
|
+
*/
|
|
3590
|
+
loadMoreQueries() {
|
|
3591
|
+
const t = this.displayedCount, e = Math.min(t + this.config.loadMoreCount, this.allQueries.length);
|
|
3592
|
+
for (let i = t; i < e; i++) {
|
|
3593
|
+
const r = this.allQueries[i];
|
|
3594
|
+
this.displayedQueries.push({
|
|
3595
|
+
query: r,
|
|
3596
|
+
order: this.allQueries.length - i,
|
|
3597
|
+
selected: !1,
|
|
3598
|
+
encodedLength: G(r).length + 1
|
|
3599
|
+
});
|
|
3600
|
+
}
|
|
3601
|
+
this.displayedCount = e, this.renderQueryList();
|
|
3602
|
+
}
|
|
3603
|
+
/**
|
|
3604
|
+
* Check if there are more queries to load.
|
|
3605
|
+
*/
|
|
3606
|
+
hasMoreQueries() {
|
|
3607
|
+
return this.displayedCount < this.allQueries.length;
|
|
3608
|
+
}
|
|
3609
|
+
/**
|
|
3610
|
+
* Toggle selection of a query.
|
|
3611
|
+
*/
|
|
3612
|
+
toggleSelection(t) {
|
|
3613
|
+
const e = this.displayedQueries[t];
|
|
3614
|
+
if (e) {
|
|
3615
|
+
if (this.selectedIndices.has(t))
|
|
3616
|
+
this.selectedIndices.delete(t), e.selected = !1;
|
|
3617
|
+
else {
|
|
3618
|
+
if (this.wouldExceedLimit(e.query)) {
|
|
3619
|
+
this.showLimitWarning();
|
|
3620
|
+
return;
|
|
3621
|
+
}
|
|
3622
|
+
this.selectedIndices.add(t), e.selected = !0;
|
|
3623
|
+
}
|
|
3624
|
+
this.updateQueryItemUI(t), this.updateFooter();
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
/**
|
|
3628
|
+
* Show warning when character limit is reached.
|
|
3629
|
+
*/
|
|
3630
|
+
showLimitWarning() {
|
|
3631
|
+
const t = this.modalContainer?.querySelector(".sharing-limit-warning");
|
|
3632
|
+
t && (t.classList.add("visible"), setTimeout(() => {
|
|
3633
|
+
t.classList.remove("visible");
|
|
3634
|
+
}, 3e3));
|
|
3635
|
+
}
|
|
3636
|
+
/**
|
|
3637
|
+
* Create the overlay DOM structure.
|
|
3638
|
+
*/
|
|
3639
|
+
createOverlay() {
|
|
3640
|
+
if (this.overlay) return;
|
|
3641
|
+
this.overlay = document.createElement("div"), this.overlay.className = "duckdb-sharing-overlay", this.overlay.style.cssText = `
|
|
3642
|
+
position: absolute;
|
|
3643
|
+
top: 0;
|
|
3644
|
+
left: 0;
|
|
3645
|
+
right: 0;
|
|
3646
|
+
bottom: 0;
|
|
3647
|
+
background: rgba(0, 0, 0, 0.85);
|
|
3648
|
+
display: flex;
|
|
3649
|
+
align-items: center;
|
|
3650
|
+
justify-content: center;
|
|
3651
|
+
z-index: 1000;
|
|
3652
|
+
opacity: 0;
|
|
3653
|
+
transition: opacity ${this.TRANSITION_DURATION}ms ease-in-out;
|
|
3654
|
+
`, this.modalContainer = document.createElement("div"), this.modalContainer.className = "duckdb-sharing-modal", this.modalContainer.style.cssText = `
|
|
3655
|
+
width: min(600px, 90vw);
|
|
3656
|
+
max-height: min(500px, 80vh);
|
|
3657
|
+
background: var(--modal-bg, #1e1e1e);
|
|
3658
|
+
border-radius: 8px;
|
|
3659
|
+
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
|
|
3660
|
+
display: flex;
|
|
3661
|
+
flex-direction: column;
|
|
3662
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
3663
|
+
color: var(--modal-text, #d4d4d4);
|
|
3664
|
+
`;
|
|
3665
|
+
const t = this.createHeader();
|
|
3666
|
+
this.modalContainer.appendChild(t), this.queryListContainer = document.createElement("div"), this.queryListContainer.className = "duckdb-sharing-query-list", this.queryListContainer.style.cssText = `
|
|
3667
|
+
flex: 1;
|
|
3668
|
+
overflow-y: auto;
|
|
3669
|
+
padding: 0 16px;
|
|
3670
|
+
min-height: 200px;
|
|
3671
|
+
`, this.modalContainer.appendChild(this.queryListContainer), this.footerContainer = this.createFooter(), this.modalContainer.appendChild(this.footerContainer);
|
|
3672
|
+
const e = document.createElement("div");
|
|
3673
|
+
e.className = "sharing-limit-warning", e.style.cssText = `
|
|
3674
|
+
position: absolute;
|
|
3675
|
+
top: 50%;
|
|
3676
|
+
left: 50%;
|
|
3677
|
+
transform: translate(-50%, -50%);
|
|
3678
|
+
background: rgba(220, 53, 69, 0.95);
|
|
3679
|
+
color: white;
|
|
3680
|
+
padding: 12px 20px;
|
|
3681
|
+
border-radius: 6px;
|
|
3682
|
+
font-size: 14px;
|
|
3683
|
+
opacity: 0;
|
|
3684
|
+
transition: opacity 0.2s;
|
|
3685
|
+
pointer-events: none;
|
|
3686
|
+
z-index: 10;
|
|
3687
|
+
`, e.textContent = "URL character limit reached (2000 chars)", this.modalContainer.appendChild(e);
|
|
3688
|
+
const i = document.createElement("style");
|
|
3689
|
+
i.textContent = `
|
|
3690
|
+
.sharing-limit-warning.visible {
|
|
3691
|
+
opacity: 1 !important;
|
|
3692
|
+
}
|
|
3693
|
+
.duckdb-sharing-query-item:hover {
|
|
3694
|
+
background: rgba(255, 255, 255, 0.05) !important;
|
|
3695
|
+
}
|
|
3696
|
+
.duckdb-sharing-query-item.focused {
|
|
3697
|
+
outline: 2px solid #007acc;
|
|
3698
|
+
outline-offset: -2px;
|
|
3699
|
+
}
|
|
3700
|
+
.duckdb-sharing-query-item.selected {
|
|
3701
|
+
background: rgba(0, 122, 204, 0.2) !important;
|
|
3702
|
+
}
|
|
3703
|
+
.duckdb-sharing-copy-btn:hover:not(:disabled) {
|
|
3704
|
+
background: #0066b3 !important;
|
|
3705
|
+
}
|
|
3706
|
+
.duckdb-sharing-copy-btn:disabled {
|
|
3707
|
+
opacity: 0.5;
|
|
3708
|
+
cursor: not-allowed;
|
|
3709
|
+
}
|
|
3710
|
+
.duckdb-sharing-load-more:hover {
|
|
3711
|
+
background: rgba(255, 255, 255, 0.1) !important;
|
|
3712
|
+
}
|
|
3713
|
+
`, this.modalContainer.appendChild(i), this.overlay.appendChild(this.modalContainer), this.setupClickHandler(), getComputedStyle(this.container).position === "static" && (this.container.style.position = "relative"), this.container.appendChild(this.overlay);
|
|
3714
|
+
}
|
|
3715
|
+
/**
|
|
3716
|
+
* Create the modal header.
|
|
3717
|
+
*/
|
|
3718
|
+
createHeader() {
|
|
3719
|
+
const t = document.createElement("div");
|
|
3720
|
+
t.style.cssText = `
|
|
3721
|
+
display: flex;
|
|
3722
|
+
justify-content: space-between;
|
|
3723
|
+
align-items: center;
|
|
3724
|
+
padding: 16px;
|
|
3725
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
3726
|
+
`;
|
|
3727
|
+
const e = document.createElement("h2");
|
|
3728
|
+
e.style.cssText = `
|
|
3729
|
+
margin: 0;
|
|
3730
|
+
font-size: 18px;
|
|
3731
|
+
font-weight: 600;
|
|
3732
|
+
color: var(--modal-title, #ffffff);
|
|
3733
|
+
`, e.textContent = "Share Queries";
|
|
3734
|
+
const i = document.createElement("button");
|
|
3735
|
+
return i.style.cssText = `
|
|
3736
|
+
background: none;
|
|
3737
|
+
border: none;
|
|
3738
|
+
color: var(--modal-text, #d4d4d4);
|
|
3739
|
+
cursor: pointer;
|
|
3740
|
+
padding: 4px 8px;
|
|
3741
|
+
font-size: 20px;
|
|
3742
|
+
line-height: 1;
|
|
3743
|
+
opacity: 0.7;
|
|
3744
|
+
transition: opacity 0.15s;
|
|
3745
|
+
`, i.innerHTML = "×", i.title = "Close (ESC)", i.addEventListener("click", () => {
|
|
3746
|
+
this.hide(), this.events.onDismiss?.();
|
|
3747
|
+
}), i.addEventListener("mouseenter", () => {
|
|
3748
|
+
i.style.opacity = "1";
|
|
3749
|
+
}), i.addEventListener("mouseleave", () => {
|
|
3750
|
+
i.style.opacity = "0.7";
|
|
3751
|
+
}), t.appendChild(e), t.appendChild(i), t;
|
|
3752
|
+
}
|
|
3753
|
+
/**
|
|
3754
|
+
* Create the modal footer.
|
|
3755
|
+
*/
|
|
3756
|
+
createFooter() {
|
|
3757
|
+
const t = document.createElement("div");
|
|
3758
|
+
t.style.cssText = `
|
|
3759
|
+
display: flex;
|
|
3760
|
+
justify-content: space-between;
|
|
3761
|
+
align-items: center;
|
|
3762
|
+
padding: 16px;
|
|
3763
|
+
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
3764
|
+
gap: 12px;
|
|
3765
|
+
`;
|
|
3766
|
+
const e = document.createElement("div");
|
|
3767
|
+
e.style.cssText = `
|
|
3768
|
+
display: flex;
|
|
3769
|
+
flex-direction: column;
|
|
3770
|
+
gap: 4px;
|
|
3771
|
+
`;
|
|
3772
|
+
const i = document.createElement("span");
|
|
3773
|
+
i.className = "sharing-char-count", i.style.cssText = `
|
|
3774
|
+
font-size: 13px;
|
|
3775
|
+
color: var(--modal-text-dim, #858585);
|
|
3776
|
+
`, i.textContent = `0 / ${this.config.maxUrlLength} characters`;
|
|
3777
|
+
const r = document.createElement("span");
|
|
3778
|
+
r.style.cssText = `
|
|
3779
|
+
font-size: 11px;
|
|
3780
|
+
color: var(--modal-text-dim, #656565);
|
|
3781
|
+
`, r.textContent = "Space: select/deselect | Enter: copy shareable link", e.appendChild(i), e.appendChild(r);
|
|
3782
|
+
const n = document.createElement("button");
|
|
3783
|
+
return n.className = "duckdb-sharing-copy-btn", n.style.cssText = `
|
|
3784
|
+
background: #007acc;
|
|
3785
|
+
border: none;
|
|
3786
|
+
color: white;
|
|
3787
|
+
padding: 10px 20px;
|
|
3788
|
+
border-radius: 6px;
|
|
3789
|
+
font-size: 14px;
|
|
3790
|
+
font-weight: 500;
|
|
3791
|
+
cursor: pointer;
|
|
3792
|
+
transition: background 0.15s;
|
|
3793
|
+
`, n.textContent = "Copy shareable link", n.disabled = !0, n.addEventListener("click", () => this.copyAndClose()), t.appendChild(e), t.appendChild(n), t;
|
|
3794
|
+
}
|
|
3795
|
+
/**
|
|
3796
|
+
* Render the query list.
|
|
3797
|
+
*/
|
|
3798
|
+
renderQueryList() {
|
|
3799
|
+
if (!this.queryListContainer) return;
|
|
3800
|
+
if (this.queryListContainer.innerHTML = "", this.hasMoreQueries()) {
|
|
3801
|
+
const e = document.createElement("button");
|
|
3802
|
+
e.className = "duckdb-sharing-load-more", e.style.cssText = `
|
|
3803
|
+
width: 100%;
|
|
3804
|
+
padding: 10px;
|
|
3805
|
+
margin: 8px 0;
|
|
3806
|
+
background: rgba(255, 255, 255, 0.05);
|
|
3807
|
+
border: 1px dashed rgba(255, 255, 255, 0.2);
|
|
3808
|
+
border-radius: 4px;
|
|
3809
|
+
color: var(--modal-text-dim, #858585);
|
|
3810
|
+
cursor: pointer;
|
|
3811
|
+
font-size: 13px;
|
|
3812
|
+
transition: background 0.15s;
|
|
3813
|
+
`, e.textContent = `Load older queries (${this.allQueries.length - this.displayedCount} more)`, e.addEventListener("click", () => this.loadMoreQueries()), this.queryListContainer.appendChild(e);
|
|
3814
|
+
}
|
|
3815
|
+
if (this.displayedQueries.length === 0) {
|
|
3816
|
+
const e = document.createElement("div");
|
|
3817
|
+
e.style.cssText = `
|
|
3818
|
+
text-align: center;
|
|
3819
|
+
padding: 40px 20px;
|
|
3820
|
+
color: var(--modal-text-dim, #858585);
|
|
3821
|
+
`, e.textContent = "No SQL queries in history yet.", this.queryListContainer.appendChild(e);
|
|
3822
|
+
return;
|
|
3823
|
+
}
|
|
3824
|
+
[...this.displayedQueries].reverse().forEach((e, i) => {
|
|
3825
|
+
const r = this.displayedQueries.length - 1 - i, n = this.displayedQueries[r], o = this.createQueryItem(n, r);
|
|
3826
|
+
this.queryListContainer.appendChild(o);
|
|
3827
|
+
}), this.queryListContainer.scrollTop = this.queryListContainer.scrollHeight;
|
|
3828
|
+
}
|
|
3829
|
+
/**
|
|
3830
|
+
* Create a query list item element.
|
|
3831
|
+
*/
|
|
3832
|
+
createQueryItem(t, e) {
|
|
3833
|
+
const i = document.createElement("div");
|
|
3834
|
+
i.className = "duckdb-sharing-query-item", i.dataset.index = String(e), i.style.cssText = `
|
|
3835
|
+
display: flex;
|
|
3836
|
+
align-items: center;
|
|
3837
|
+
padding: 10px 12px;
|
|
3838
|
+
margin: 4px 0;
|
|
3839
|
+
background: rgba(255, 255, 255, 0.02);
|
|
3840
|
+
border-radius: 4px;
|
|
3841
|
+
cursor: pointer;
|
|
3842
|
+
transition: background 0.15s;
|
|
3843
|
+
gap: 12px;
|
|
3844
|
+
`, t.selected && i.classList.add("selected");
|
|
3845
|
+
const r = document.createElement("span");
|
|
3846
|
+
r.className = "query-order", r.style.cssText = `
|
|
3847
|
+
min-width: 28px;
|
|
3848
|
+
height: 28px;
|
|
3849
|
+
display: flex;
|
|
3850
|
+
align-items: center;
|
|
3851
|
+
justify-content: center;
|
|
3852
|
+
background: rgba(255, 255, 255, 0.1);
|
|
3853
|
+
border-radius: 4px;
|
|
3854
|
+
font-size: 12px;
|
|
3855
|
+
font-weight: 500;
|
|
3856
|
+
color: var(--modal-text-dim, #858585);
|
|
3857
|
+
`, r.textContent = String(t.order);
|
|
3858
|
+
const n = document.createElement("div");
|
|
3859
|
+
n.className = "query-checkbox", n.style.cssText = `
|
|
3860
|
+
width: 18px;
|
|
3861
|
+
height: 18px;
|
|
3862
|
+
border: 2px solid ${t.selected ? "#007acc" : "rgba(255, 255, 255, 0.3)"};
|
|
3863
|
+
border-radius: 3px;
|
|
3864
|
+
background: ${t.selected ? "#007acc" : "transparent"};
|
|
3865
|
+
display: flex;
|
|
3866
|
+
align-items: center;
|
|
3867
|
+
justify-content: center;
|
|
3868
|
+
transition: all 0.15s;
|
|
3869
|
+
flex-shrink: 0;
|
|
3870
|
+
`, t.selected && (n.innerHTML = `<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
|
|
3871
|
+
<path d="M2 6L5 9L10 3" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
3872
|
+
</svg>`);
|
|
3873
|
+
const o = document.createElement("div");
|
|
3874
|
+
return o.className = "query-text", o.style.cssText = `
|
|
3875
|
+
flex: 1;
|
|
3876
|
+
font-family: 'Fira Code', 'Cascadia Code', monospace;
|
|
3877
|
+
font-size: 13px;
|
|
3878
|
+
color: var(--modal-text, #d4d4d4);
|
|
3879
|
+
white-space: nowrap;
|
|
3880
|
+
overflow: hidden;
|
|
3881
|
+
text-overflow: ellipsis;
|
|
3882
|
+
`, o.textContent = t.query, o.title = t.query, i.appendChild(r), i.appendChild(n), i.appendChild(o), i.addEventListener("click", () => {
|
|
3883
|
+
this.focusedIndex = e, this.updateFocusedItem(), this.toggleSelection(e);
|
|
3884
|
+
}), i;
|
|
3885
|
+
}
|
|
3886
|
+
/**
|
|
3887
|
+
* Update a single query item's UI after selection change.
|
|
3888
|
+
*/
|
|
3889
|
+
updateQueryItemUI(t) {
|
|
3890
|
+
const e = this.displayedQueries[t];
|
|
3891
|
+
if (!e || !this.queryListContainer) return;
|
|
3892
|
+
const i = this.queryListContainer.querySelector(
|
|
3893
|
+
`[data-index="${t}"]`
|
|
3894
|
+
);
|
|
3895
|
+
if (!i) return;
|
|
3896
|
+
const r = i.querySelector(".query-checkbox");
|
|
3897
|
+
r && (r.style.borderColor = e.selected ? "#007acc" : "rgba(255, 255, 255, 0.3)", r.style.background = e.selected ? "#007acc" : "transparent", r.innerHTML = e.selected ? `<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
|
|
3898
|
+
<path d="M2 6L5 9L10 3" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
3899
|
+
</svg>` : ""), e.selected ? i.classList.add("selected") : i.classList.remove("selected");
|
|
3900
|
+
}
|
|
3901
|
+
/**
|
|
3902
|
+
* Update the footer with current character count.
|
|
3903
|
+
*/
|
|
3904
|
+
updateFooter() {
|
|
3905
|
+
if (!this.footerContainer) return;
|
|
3906
|
+
const t = this.footerContainer.querySelector(".sharing-char-count"), e = this.footerContainer.querySelector(".duckdb-sharing-copy-btn"), i = this.getCurrentURLLength(), r = this.selectedIndices.size;
|
|
3907
|
+
t && (t.textContent = `${i} / ${this.config.maxUrlLength} characters`, i > this.config.maxUrlLength * 0.9 ? t.style.color = "#dc3545" : i > this.config.maxUrlLength * 0.7 ? t.style.color = "#ffc107" : t.style.color = "var(--modal-text-dim, #858585)"), e && (e.disabled = r === 0, e.textContent = r > 0 ? `Copy shareable link (${r} ${r === 1 ? "query" : "queries"})` : "Copy shareable link");
|
|
3908
|
+
}
|
|
3909
|
+
/**
|
|
3910
|
+
* Update focused item styling.
|
|
3911
|
+
*/
|
|
3912
|
+
updateFocusedItem() {
|
|
3913
|
+
if (!this.queryListContainer) return;
|
|
3914
|
+
if (this.queryListContainer.querySelectorAll(".duckdb-sharing-query-item").forEach((e) => e.classList.remove("focused")), this.focusedIndex >= 0) {
|
|
3915
|
+
const e = this.queryListContainer.querySelector(
|
|
3916
|
+
`[data-index="${this.focusedIndex}"]`
|
|
3917
|
+
);
|
|
3918
|
+
e && (e.classList.add("focused"), e.scrollIntoView({ block: "nearest" }));
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
/**
|
|
3922
|
+
* Copy shareable link and close modal.
|
|
3923
|
+
*/
|
|
3924
|
+
async copyAndClose() {
|
|
3925
|
+
const t = this.getSelectedQueries();
|
|
3926
|
+
if (t.length === 0) return;
|
|
3927
|
+
const { url: e } = q(t);
|
|
3928
|
+
await st(e) && (this.events.onCopy?.(e), await this.hide());
|
|
3929
|
+
}
|
|
3930
|
+
/**
|
|
3931
|
+
* Remove overlay from DOM and clean up.
|
|
3932
|
+
*/
|
|
3933
|
+
destroyOverlay() {
|
|
3934
|
+
this.removeKeyboardHandler(), this.removeClickHandler(), this.overlay && this.overlay.parentNode && this.overlay.parentNode.removeChild(this.overlay), this.overlay = null, this.modalContainer = null, this.queryListContainer = null, this.footerContainer = null, this.displayedQueries = [], this.selectedIndices.clear();
|
|
3935
|
+
}
|
|
3936
|
+
/**
|
|
3937
|
+
* Set up keyboard event handler.
|
|
3938
|
+
*/
|
|
3939
|
+
setupKeyboardHandler() {
|
|
3940
|
+
this.keyHandler = (t) => {
|
|
3941
|
+
if (t.key === "Escape") {
|
|
3942
|
+
t.preventDefault(), t.stopPropagation(), this.hide(), this.events.onDismiss?.();
|
|
3943
|
+
return;
|
|
3944
|
+
}
|
|
3945
|
+
if (t.key === "Enter" && this.selectedIndices.size > 0) {
|
|
3946
|
+
t.preventDefault(), t.stopPropagation(), this.copyAndClose();
|
|
3947
|
+
return;
|
|
3948
|
+
}
|
|
3949
|
+
if (t.key === " " && this.focusedIndex >= 0) {
|
|
3950
|
+
t.preventDefault(), t.stopPropagation(), this.toggleSelection(this.focusedIndex);
|
|
3951
|
+
return;
|
|
3952
|
+
}
|
|
3953
|
+
if (t.key === "ArrowDown") {
|
|
3954
|
+
t.preventDefault(), t.stopPropagation(), this.moveFocusDown();
|
|
3955
|
+
return;
|
|
3956
|
+
}
|
|
3957
|
+
if (t.key === "ArrowUp") {
|
|
3958
|
+
t.preventDefault(), t.stopPropagation(), this.moveFocusUp();
|
|
3959
|
+
return;
|
|
3960
|
+
}
|
|
3961
|
+
}, document.addEventListener("keydown", this.keyHandler, !0);
|
|
3962
|
+
}
|
|
3963
|
+
/**
|
|
3964
|
+
* Move focus down (to newer queries).
|
|
3965
|
+
* In display order, newer queries are at the bottom (lower display indices).
|
|
3966
|
+
*/
|
|
3967
|
+
moveFocusDown() {
|
|
3968
|
+
if (this.displayedQueries.length === 0) return;
|
|
3969
|
+
const t = this.focusedIndex - 1;
|
|
3970
|
+
t >= 0 && (this.focusedIndex = t, this.updateFocusedItem());
|
|
3971
|
+
}
|
|
3972
|
+
/**
|
|
3973
|
+
* Move focus up (to older queries).
|
|
3974
|
+
* In display order, older queries are at the top (higher display indices).
|
|
3975
|
+
* Auto-loads more queries when reaching the top.
|
|
3976
|
+
*/
|
|
3977
|
+
moveFocusUp() {
|
|
3978
|
+
if (this.displayedQueries.length === 0) return;
|
|
3979
|
+
const t = this.focusedIndex + 1;
|
|
3980
|
+
t < this.displayedQueries.length ? (this.focusedIndex = t, this.updateFocusedItem()) : this.hasMoreQueries() && (this.loadMoreQueries(), this.focusedIndex = this.displayedQueries.length - 1, this.updateFocusedItem());
|
|
3981
|
+
}
|
|
3982
|
+
/**
|
|
3983
|
+
* Remove keyboard event handler.
|
|
3984
|
+
*/
|
|
3985
|
+
removeKeyboardHandler() {
|
|
3986
|
+
this.keyHandler && (document.removeEventListener("keydown", this.keyHandler, !0), this.keyHandler = null);
|
|
3987
|
+
}
|
|
3988
|
+
/**
|
|
3989
|
+
* Set up click handler to close when clicking outside the modal.
|
|
3990
|
+
*/
|
|
3991
|
+
setupClickHandler() {
|
|
3992
|
+
this.clickHandler = (t) => {
|
|
3993
|
+
t.target === this.overlay && (t.preventDefault(), t.stopPropagation(), this.hide(), this.events.onDismiss?.());
|
|
3994
|
+
}, this.overlay?.addEventListener("click", this.clickHandler);
|
|
3995
|
+
}
|
|
3996
|
+
/**
|
|
3997
|
+
* Remove click handler.
|
|
3998
|
+
*/
|
|
3999
|
+
removeClickHandler() {
|
|
4000
|
+
this.clickHandler && this.overlay && this.overlay.removeEventListener("click", this.clickHandler), this.clickHandler = null;
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
const wi = {
|
|
4004
|
+
background: "#1e1e1e",
|
|
4005
|
+
foreground: "#d4d4d4",
|
|
4006
|
+
cursor: "#d4d4d4",
|
|
4007
|
+
selection: "#264f78",
|
|
4008
|
+
black: "#000000",
|
|
4009
|
+
red: "#cd3131",
|
|
4010
|
+
green: "#0dbc79",
|
|
4011
|
+
yellow: "#e5e510",
|
|
4012
|
+
blue: "#2472c8",
|
|
4013
|
+
magenta: "#bc3fbc",
|
|
4014
|
+
cyan: "#11a8cd",
|
|
4015
|
+
white: "#e5e5e5",
|
|
4016
|
+
brightBlack: "#666666",
|
|
4017
|
+
brightRed: "#f14c4c",
|
|
4018
|
+
brightGreen: "#23d18b",
|
|
4019
|
+
brightYellow: "#f5f543",
|
|
4020
|
+
brightBlue: "#3b8eea",
|
|
4021
|
+
brightMagenta: "#d670d6",
|
|
4022
|
+
brightCyan: "#29b8db",
|
|
4023
|
+
brightWhite: "#ffffff"
|
|
4024
|
+
}, yi = {
|
|
4025
|
+
background: "#ffffff",
|
|
4026
|
+
foreground: "#1e1e1e",
|
|
4027
|
+
cursor: "#1e1e1e",
|
|
4028
|
+
selection: "#add6ff",
|
|
4029
|
+
black: "#000000",
|
|
4030
|
+
red: "#cd3131",
|
|
4031
|
+
green: "#008000",
|
|
4032
|
+
yellow: "#795e00",
|
|
4033
|
+
blue: "#0451a5",
|
|
4034
|
+
magenta: "#bc05bc",
|
|
4035
|
+
cyan: "#0598bc",
|
|
4036
|
+
white: "#e5e5e5",
|
|
4037
|
+
brightBlack: "#666666",
|
|
4038
|
+
brightRed: "#cd3131",
|
|
4039
|
+
brightGreen: "#14ce14",
|
|
4040
|
+
brightYellow: "#b5ba00",
|
|
4041
|
+
brightBlue: "#0451a5",
|
|
4042
|
+
brightMagenta: "#bc05bc",
|
|
4043
|
+
brightCyan: "#0598bc",
|
|
4044
|
+
brightWhite: "#a5a5a5"
|
|
4045
|
+
}, bi = {
|
|
4046
|
+
name: "dark",
|
|
4047
|
+
colors: wi
|
|
4048
|
+
}, Ci = {
|
|
4049
|
+
name: "light",
|
|
4050
|
+
colors: yi
|
|
4051
|
+
};
|
|
4052
|
+
function pt(s) {
|
|
4053
|
+
return s === "light" ? Ci : bi;
|
|
4054
|
+
}
|
|
4055
|
+
function xi() {
|
|
4056
|
+
try {
|
|
4057
|
+
const s = localStorage.getItem("duckdb-terminal-theme");
|
|
4058
|
+
if (s === "light" || s === "dark")
|
|
4059
|
+
return s;
|
|
4060
|
+
} catch {
|
|
4061
|
+
}
|
|
4062
|
+
return "dark";
|
|
4063
|
+
}
|
|
4064
|
+
function vi(s) {
|
|
4065
|
+
try {
|
|
4066
|
+
localStorage.setItem("duckdb-terminal-theme", s);
|
|
4067
|
+
} catch {
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
4070
|
+
const rt = "duckdb-terminal-ai-settings", J = "http://localhost:4000", Z = "claude";
|
|
4071
|
+
function gt() {
|
|
4072
|
+
return J;
|
|
4073
|
+
}
|
|
4074
|
+
function Ti() {
|
|
4075
|
+
return Z;
|
|
4076
|
+
}
|
|
4077
|
+
function Rt() {
|
|
4078
|
+
try {
|
|
4079
|
+
const s = localStorage.getItem(rt);
|
|
4080
|
+
if (s) {
|
|
4081
|
+
const t = JSON.parse(s);
|
|
4082
|
+
return {
|
|
4083
|
+
endpoint: t.endpoint || J,
|
|
4084
|
+
provider: t.provider || Z
|
|
4085
|
+
};
|
|
4086
|
+
}
|
|
4087
|
+
} catch {
|
|
4088
|
+
}
|
|
4089
|
+
return { endpoint: J, provider: Z };
|
|
4090
|
+
}
|
|
4091
|
+
function O(s) {
|
|
4092
|
+
try {
|
|
4093
|
+
const e = { ...Rt(), ...s };
|
|
4094
|
+
localStorage.setItem(rt, JSON.stringify(e));
|
|
4095
|
+
} catch {
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
4098
|
+
function Xi() {
|
|
4099
|
+
try {
|
|
4100
|
+
localStorage.removeItem(rt);
|
|
4101
|
+
} catch {
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
class P extends Error {
|
|
4105
|
+
constructor(t, e, i) {
|
|
4106
|
+
super(t), this.statusCode = e, this.endpoint = i, this.name = "AIClientError";
|
|
4107
|
+
}
|
|
4108
|
+
}
|
|
4109
|
+
function nt(s) {
|
|
4110
|
+
return s.replace(/\/$/, "");
|
|
4111
|
+
}
|
|
4112
|
+
async function X(s) {
|
|
4113
|
+
const t = `${nt(s)}/providers`;
|
|
4114
|
+
try {
|
|
4115
|
+
return (await fetch(t, {
|
|
4116
|
+
method: "GET",
|
|
4117
|
+
headers: {
|
|
4118
|
+
Accept: "application/json"
|
|
4119
|
+
}
|
|
4120
|
+
})).ok;
|
|
4121
|
+
} catch {
|
|
4122
|
+
return !1;
|
|
4123
|
+
}
|
|
4124
|
+
}
|
|
4125
|
+
async function wt(s) {
|
|
4126
|
+
const t = `${nt(s)}/providers`;
|
|
4127
|
+
try {
|
|
4128
|
+
const e = await fetch(t, {
|
|
4129
|
+
method: "GET",
|
|
4130
|
+
headers: {
|
|
4131
|
+
Accept: "application/json"
|
|
4132
|
+
}
|
|
4133
|
+
});
|
|
4134
|
+
if (!e.ok)
|
|
4135
|
+
throw new P(
|
|
4136
|
+
`Failed to fetch providers: ${e.statusText}`,
|
|
4137
|
+
e.status,
|
|
4138
|
+
t
|
|
4139
|
+
);
|
|
4140
|
+
return (await e.json()).providers || [];
|
|
4141
|
+
} catch (e) {
|
|
4142
|
+
throw e instanceof P ? e : new P(
|
|
4143
|
+
`Network error connecting to ${t}: ${e instanceof Error ? e.message : "Unknown error"}`,
|
|
4144
|
+
void 0,
|
|
4145
|
+
t
|
|
4146
|
+
);
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
async function Ei(s, t, e, i) {
|
|
4150
|
+
const r = `${nt(s)}/generate-sql`, n = {
|
|
4151
|
+
ddl: t,
|
|
4152
|
+
question: e
|
|
4153
|
+
};
|
|
4154
|
+
i && (n.provider = i);
|
|
4155
|
+
try {
|
|
4156
|
+
const o = await fetch(r, {
|
|
4157
|
+
method: "POST",
|
|
4158
|
+
headers: {
|
|
4159
|
+
"Content-Type": "application/json",
|
|
4160
|
+
Accept: "application/json"
|
|
4161
|
+
},
|
|
4162
|
+
body: JSON.stringify(n)
|
|
4163
|
+
});
|
|
4164
|
+
if (!o.ok) {
|
|
4165
|
+
const l = await o.text().catch(() => o.statusText);
|
|
4166
|
+
throw new P(`Failed to generate SQL: ${l}`, o.status, r);
|
|
4167
|
+
}
|
|
4168
|
+
return (await o.json()).sql;
|
|
4169
|
+
} catch (o) {
|
|
4170
|
+
throw o instanceof P ? o : new P(
|
|
4171
|
+
`Network error connecting to ${r}: ${o instanceof Error ? o.message : "Unknown error"}`,
|
|
4172
|
+
void 0,
|
|
4173
|
+
r
|
|
4174
|
+
);
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4177
|
+
const yt = "🦆 ", bt = " > ";
|
|
4178
|
+
class Si {
|
|
4179
|
+
constructor(t) {
|
|
4180
|
+
h(this, "terminalAdapter");
|
|
4181
|
+
h(this, "database");
|
|
4182
|
+
h(this, "inputBuffer");
|
|
4183
|
+
h(this, "history");
|
|
4184
|
+
h(this, "state", "idle");
|
|
4185
|
+
h(this, "collectedSQL", []);
|
|
4186
|
+
h(this, "commands", /* @__PURE__ */ new Map());
|
|
4187
|
+
h(this, "showTimer", !1);
|
|
4188
|
+
h(this, "outputMode", "table");
|
|
4189
|
+
h(this, "currentThemeName");
|
|
4190
|
+
h(this, "customTheme", null);
|
|
4191
|
+
h(this, "config");
|
|
4192
|
+
h(this, "loadedFiles", /* @__PURE__ */ new Map());
|
|
4193
|
+
h(this, "syntaxHighlighting", !0);
|
|
4194
|
+
h(this, "lastQueryResult", null);
|
|
4195
|
+
h(this, "linkProvider");
|
|
4196
|
+
h(this, "chartManager", null);
|
|
4197
|
+
h(this, "sharingModal", null);
|
|
4198
|
+
// Event emitter
|
|
4199
|
+
h(this, "eventListeners", /* @__PURE__ */ new Map());
|
|
4200
|
+
// Pagination state (0 = disabled by default)
|
|
4201
|
+
h(this, "pageSize", 0);
|
|
4202
|
+
h(this, "paginationQuery", null);
|
|
4203
|
+
h(this, "currentPage", 0);
|
|
4204
|
+
h(this, "totalRows", 0);
|
|
4205
|
+
// Prompt customization
|
|
4206
|
+
h(this, "prompt");
|
|
4207
|
+
h(this, "continuationPrompt");
|
|
4208
|
+
// Debounced syntax highlighting (150ms delay to avoid excessive redraws)
|
|
4209
|
+
h(this, "debouncedHighlight", Ee(() => this.redrawLineHighlighted(), 150));
|
|
4210
|
+
// Query queue to prevent race conditions
|
|
4211
|
+
h(this, "queryQueue", Promise.resolve(null));
|
|
4212
|
+
// Cleanup function for drag-and-drop
|
|
4213
|
+
h(this, "dragDropCleanup", null);
|
|
4214
|
+
// AI settings (lazy-loaded to avoid localStorage access on construction)
|
|
4215
|
+
h(this, "aiSettingsLoaded", !1);
|
|
4216
|
+
h(this, "aiEndpoint", "http://localhost:4000");
|
|
4217
|
+
h(this, "aiProvider", "claude");
|
|
4218
|
+
this.config = t, this.terminalAdapter = new jt(), this.database = new Wt({
|
|
4219
|
+
storage: t.storage ?? "memory",
|
|
4220
|
+
databasePath: t.databasePath
|
|
4221
|
+
}), this.inputBuffer = new ce(), this.history = new ue(), this.linkProvider = new Re(), t.linkDetection === !1 && this.linkProvider.setEnabled(!1), this.prompt = t.prompt ?? yt, this.continuationPrompt = t.continuationPrompt ?? bt, typeof t.theme == "object" ? (this.customTheme = t.theme, this.currentThemeName = "custom") : this.currentThemeName = t.theme ?? xi(), this.registerCommands();
|
|
4222
|
+
}
|
|
4223
|
+
// ==================== Event Emitter ====================
|
|
4224
|
+
/**
|
|
4225
|
+
* Subscribes to a terminal event.
|
|
4226
|
+
*
|
|
4227
|
+
* The terminal emits various events during its lifecycle that you can
|
|
4228
|
+
* subscribe to for monitoring, logging, or integrating with your application.
|
|
4229
|
+
*
|
|
4230
|
+
* @typeParam K - The event type key from {@link TerminalEvents}
|
|
4231
|
+
* @param event - The event name to subscribe to
|
|
4232
|
+
* @param listener - The callback function to invoke when the event occurs
|
|
4233
|
+
* @returns An unsubscribe function that removes the listener when called
|
|
4234
|
+
*
|
|
4235
|
+
* @example Subscribe to query events
|
|
4236
|
+
* ```typescript
|
|
4237
|
+
* const unsubscribe = terminal.on('queryEnd', ({ sql, result, duration }) => {
|
|
4238
|
+
* console.log(`Query completed in ${duration}ms`);
|
|
4239
|
+
* });
|
|
4240
|
+
*
|
|
4241
|
+
* // Later, to stop listening:
|
|
4242
|
+
* unsubscribe();
|
|
4243
|
+
* ```
|
|
4244
|
+
*
|
|
4245
|
+
* @example Monitor state changes
|
|
4246
|
+
* ```typescript
|
|
4247
|
+
* terminal.on('stateChange', ({ state, previous }) => {
|
|
4248
|
+
* console.log(`Terminal state: ${previous} -> ${state}`);
|
|
4249
|
+
* });
|
|
4250
|
+
* ```
|
|
4251
|
+
*/
|
|
4252
|
+
on(t, e) {
|
|
4253
|
+
return this.eventListeners.has(t) || this.eventListeners.set(t, /* @__PURE__ */ new Set()), this.eventListeners.get(t).add(e), () => this.off(t, e);
|
|
4254
|
+
}
|
|
4255
|
+
/**
|
|
4256
|
+
* Unsubscribes a listener from a terminal event.
|
|
4257
|
+
*
|
|
4258
|
+
* This is an alternative to using the unsubscribe function returned by {@link on}.
|
|
4259
|
+
*
|
|
4260
|
+
* @typeParam K - The event type key from {@link TerminalEvents}
|
|
4261
|
+
* @param event - The event name to unsubscribe from
|
|
4262
|
+
* @param listener - The callback function to remove
|
|
4263
|
+
*
|
|
4264
|
+
* @example
|
|
4265
|
+
* ```typescript
|
|
4266
|
+
* const handler = ({ sql }) => console.log(sql);
|
|
4267
|
+
* terminal.on('queryStart', handler);
|
|
4268
|
+
*
|
|
4269
|
+
* // Later:
|
|
4270
|
+
* terminal.off('queryStart', handler);
|
|
4271
|
+
* ```
|
|
4272
|
+
*/
|
|
4273
|
+
off(t, e) {
|
|
4274
|
+
this.eventListeners.get(t)?.delete(e);
|
|
4275
|
+
}
|
|
4276
|
+
/**
|
|
4277
|
+
* Emits an event to all registered listeners.
|
|
4278
|
+
*
|
|
4279
|
+
* @internal
|
|
4280
|
+
* @typeParam K - The event type key from {@link TerminalEvents}
|
|
4281
|
+
* @param event - The event name to emit
|
|
4282
|
+
* @param payload - The event payload to pass to listeners
|
|
4283
|
+
*/
|
|
4284
|
+
emit(t, e) {
|
|
4285
|
+
this.eventListeners.get(t)?.forEach((i) => {
|
|
4286
|
+
try {
|
|
4287
|
+
i(e);
|
|
4288
|
+
} catch (r) {
|
|
4289
|
+
console.error(`Error in ${t} event listener:`, r);
|
|
4290
|
+
}
|
|
4291
|
+
});
|
|
4292
|
+
}
|
|
4293
|
+
/**
|
|
4294
|
+
* Sets the terminal state and emits a stateChange event.
|
|
4295
|
+
*
|
|
4296
|
+
* @internal
|
|
4297
|
+
* @param newState - The new terminal state
|
|
4298
|
+
*/
|
|
4299
|
+
setState(t) {
|
|
4300
|
+
const e = this.state;
|
|
4301
|
+
e !== t && (this.state = t, this.emit("stateChange", { state: t, previous: e }));
|
|
4302
|
+
}
|
|
4303
|
+
// ==================== Syntax Highlighting ====================
|
|
4304
|
+
/**
|
|
4305
|
+
* Asynchronously highlights SQL using DuckDB's tokenizer.
|
|
4306
|
+
*
|
|
4307
|
+
* @param sql - The SQL string to highlight
|
|
4308
|
+
* @returns The highlighted SQL string with ANSI color codes
|
|
4309
|
+
*/
|
|
4310
|
+
async getHighlightedSQLAsync(t) {
|
|
4311
|
+
if (!this.syntaxHighlighting)
|
|
4312
|
+
return t;
|
|
4313
|
+
if (this.database.isPoachedLoaded()) {
|
|
4314
|
+
const e = await this.database.tokenizeSQL(t);
|
|
4315
|
+
if (e)
|
|
4316
|
+
return K(t, e);
|
|
4317
|
+
}
|
|
4318
|
+
return t;
|
|
4319
|
+
}
|
|
4320
|
+
/**
|
|
4321
|
+
* Redraws the current input line with syntax highlighting applied.
|
|
4322
|
+
*
|
|
4323
|
+
* This method clears the current line content and rewrites it with
|
|
4324
|
+
* color codes for SQL keywords, strings, numbers, etc. Called on
|
|
4325
|
+
* delimiter characters (space, semicolon, parentheses, comma) via debouncing.
|
|
4326
|
+
*
|
|
4327
|
+
* Uses DuckDB's internal parser via the poached extension for accurate tokenization.
|
|
4328
|
+
*/
|
|
4329
|
+
async redrawLineHighlighted() {
|
|
4330
|
+
if (!this.syntaxHighlighting)
|
|
4331
|
+
return;
|
|
4332
|
+
const t = this.inputBuffer.getContent();
|
|
4333
|
+
if (t.length === 0)
|
|
4334
|
+
return;
|
|
4335
|
+
const e = this.inputBuffer.getCursorPosition(), i = this.inputBuffer.getEndPosition();
|
|
2065
4336
|
let r;
|
|
2066
4337
|
if (this.database.isPoachedLoaded()) {
|
|
2067
4338
|
const c = await this.database.tokenizeSQL(t);
|
|
2068
|
-
c ? r =
|
|
4339
|
+
c ? r = K(t, c) : r = t;
|
|
2069
4340
|
} else
|
|
2070
4341
|
r = t;
|
|
2071
4342
|
if (t !== this.inputBuffer.getContent())
|
|
2072
4343
|
return;
|
|
2073
4344
|
const n = { col: this.inputBuffer.getPromptLength() };
|
|
2074
4345
|
let o = "";
|
|
2075
|
-
e.row > 0 && (o +=
|
|
4346
|
+
e.row > 0 && (o += D(e.row)), o += E(n.col + 1), this.write(o), this.write(C);
|
|
2076
4347
|
for (let c = 1; c <= i.row; c++)
|
|
2077
|
-
this.write(
|
|
2078
|
-
i.row > 0 && this.write(
|
|
4348
|
+
this.write(I(1)), this.write(E(1)), this.write(C);
|
|
4349
|
+
i.row > 0 && this.write(D(i.row)), this.write(E(n.col + 1)), this.write(r);
|
|
2079
4350
|
const a = this.inputBuffer.getEndPosition();
|
|
2080
4351
|
let l = "";
|
|
2081
|
-
a.row > e.row && (l +=
|
|
4352
|
+
a.row > e.row && (l += D(a.row - e.row)), l += E(e.col + 1), this.write(l);
|
|
2082
4353
|
}
|
|
2083
4354
|
/**
|
|
2084
4355
|
* Returns the current theme object for terminal styling.
|
|
@@ -2086,7 +4357,7 @@ class te {
|
|
|
2086
4357
|
* @returns The custom theme if set, otherwise the built-in theme ('dark' or 'light')
|
|
2087
4358
|
*/
|
|
2088
4359
|
getCurrentThemeObject() {
|
|
2089
|
-
return this.customTheme ? this.customTheme :
|
|
4360
|
+
return this.customTheme ? this.customTheme : pt(this.currentThemeName);
|
|
2090
4361
|
}
|
|
2091
4362
|
/**
|
|
2092
4363
|
* Initializes and starts the terminal.
|
|
@@ -2120,12 +4391,12 @@ class te {
|
|
|
2120
4391
|
fontSize: this.config.fontSize,
|
|
2121
4392
|
theme: this.getCurrentThemeObject(),
|
|
2122
4393
|
scrollback: this.config.scrollback
|
|
2123
|
-
}), this.config.welcomeMessage !== !1 && (this.writeln(
|
|
4394
|
+
}), this.config.welcomeMessage !== !1 && (this.write(se), this.writeln(""), this.writeln(L("DuckDB Terminal") + " v0.4.2"), this.write(d("Loading DuckDB WASM...", T))), await Promise.all([
|
|
2124
4395
|
this.database.init(),
|
|
2125
4396
|
this.history.init()
|
|
2126
|
-
]), await this.database.loadPoachedExtension(), this.config.welcomeMessage !== !1 && (this.write("\r" +
|
|
4397
|
+
]), await this.database.loadPoachedExtension(), this.config.welcomeMessage !== !1 && (this.write(re), this.write("\r" + C), this.writeln(d("Powered by DuckDB WASM and Ghostty", T)), this.writeln(""), this.writeln("Type " + d(".help", v) + " for available commands"), this.writeln("Enter SQL statements ending with " + d(";", F)), this.writeln("")), this.terminalAdapter.onData(this.handleInput.bind(this)), this.inputBuffer.setTerminalWidth(this.terminalAdapter.cols), this.terminalAdapter.onResize((e) => {
|
|
2127
4398
|
this.handleResize(e);
|
|
2128
|
-
}), this.dragDropCleanup =
|
|
4399
|
+
}), this.dragDropCleanup = Ce(t, (e) => {
|
|
2129
4400
|
this.handleDroppedFiles(e);
|
|
2130
4401
|
}), this.showPrompt(), this.emit("ready", {});
|
|
2131
4402
|
}
|
|
@@ -2136,7 +4407,32 @@ class te {
|
|
|
2136
4407
|
* This removes drag-and-drop handlers and clears internal state.
|
|
2137
4408
|
*/
|
|
2138
4409
|
destroy() {
|
|
2139
|
-
this.dragDropCleanup && (this.dragDropCleanup(), this.dragDropCleanup = null), this.eventListeners.clear(), this.debouncedHighlight.cancel();
|
|
4410
|
+
this.dragDropCleanup && (this.dragDropCleanup(), this.dragDropCleanup = null), this.chartManager && (this.chartManager.destroy(), this.chartManager = null), this.sharingModal && (this.sharingModal.destroy(), this.sharingModal = null), this.eventListeners.clear(), this.debouncedHighlight.cancel();
|
|
4411
|
+
}
|
|
4412
|
+
/**
|
|
4413
|
+
* Opens the sharing modal to select queries for sharing.
|
|
4414
|
+
*
|
|
4415
|
+
* The modal displays the command history (excluding dot commands) and allows
|
|
4416
|
+
* the user to select queries to include in a shareable URL.
|
|
4417
|
+
*/
|
|
4418
|
+
openSharingModal() {
|
|
4419
|
+
if (this.state === "executing")
|
|
4420
|
+
return;
|
|
4421
|
+
if (!this.sharingModal) {
|
|
4422
|
+
const i = this.resolveContainer();
|
|
4423
|
+
this.sharingModal = new gi(
|
|
4424
|
+
i,
|
|
4425
|
+
{},
|
|
4426
|
+
// events
|
|
4427
|
+
{
|
|
4428
|
+
maxUrlLength: 2e3,
|
|
4429
|
+
initialQueryCount: 5,
|
|
4430
|
+
loadMoreCount: 5
|
|
4431
|
+
}
|
|
4432
|
+
);
|
|
4433
|
+
}
|
|
4434
|
+
const e = this.history.getAll().filter((i) => !i.trim().startsWith("."));
|
|
4435
|
+
this.sharingModal.show(e);
|
|
2140
4436
|
}
|
|
2141
4437
|
/**
|
|
2142
4438
|
* Processes files dropped onto the terminal via drag-and-drop.
|
|
@@ -2150,7 +4446,7 @@ class te {
|
|
|
2150
4446
|
t.map((n) => this.loadFile(n))
|
|
2151
4447
|
), i = e.filter((n) => n.status === "fulfilled" && n.value).length, r = e.length - i;
|
|
2152
4448
|
r > 0 && i > 0 && this.writeln(
|
|
2153
|
-
|
|
4449
|
+
m(`Loaded ${i} of ${e.length} files (${r} failed)`)
|
|
2154
4450
|
), this.showPrompt();
|
|
2155
4451
|
}
|
|
2156
4452
|
/**
|
|
@@ -2163,33 +4459,33 @@ class te {
|
|
|
2163
4459
|
* @returns True if the file was loaded successfully, false otherwise
|
|
2164
4460
|
*/
|
|
2165
4461
|
async loadFile(t) {
|
|
2166
|
-
const e =
|
|
4462
|
+
const e = Et(t.name), i = ye(t);
|
|
2167
4463
|
try {
|
|
2168
|
-
const r = await
|
|
4464
|
+
const r = await we(t);
|
|
2169
4465
|
await this.database.registerFile(t.name, r), this.loadedFiles.set(t.name, i), this.emit("fileLoaded", {
|
|
2170
4466
|
filename: t.name,
|
|
2171
4467
|
size: t.size,
|
|
2172
4468
|
type: e
|
|
2173
4469
|
}), this.writeln(""), this.writeln(
|
|
2174
|
-
|
|
4470
|
+
d(`Loaded: ${t.name}`, b) + ` (${U(t.size)})`
|
|
2175
4471
|
);
|
|
2176
4472
|
const n = t.name.replace(/'/g, "''");
|
|
2177
4473
|
if (e === "csv") {
|
|
2178
4474
|
const o = t.name.replace(/\.[^.]+$/, "").replace(/[^a-zA-Z0-9_]/g, "_");
|
|
2179
4475
|
this.writeln(
|
|
2180
|
-
|
|
4476
|
+
m(`Hint: SELECT * FROM read_csv('${n}');`)
|
|
2181
4477
|
), this.writeln(
|
|
2182
|
-
|
|
4478
|
+
m(` or: CREATE TABLE ${o} AS SELECT * FROM read_csv('${n}');`)
|
|
2183
4479
|
);
|
|
2184
4480
|
} else e === "parquet" ? this.writeln(
|
|
2185
|
-
|
|
4481
|
+
m(`Hint: SELECT * FROM read_parquet('${n}');`)
|
|
2186
4482
|
) : e === "json" && this.writeln(
|
|
2187
|
-
|
|
4483
|
+
m(`Hint: SELECT * FROM read_json('${n}');`)
|
|
2188
4484
|
);
|
|
2189
4485
|
return !0;
|
|
2190
4486
|
} catch (r) {
|
|
2191
4487
|
const n = r instanceof Error ? r.message : String(r);
|
|
2192
|
-
return this.writeln(
|
|
4488
|
+
return this.writeln(d(`Error loading ${t.name}: ${n}`, w)), !1;
|
|
2193
4489
|
}
|
|
2194
4490
|
}
|
|
2195
4491
|
/**
|
|
@@ -2265,6 +4561,11 @@ class te {
|
|
|
2265
4561
|
handler: async () => {
|
|
2266
4562
|
await this.copyLastResult();
|
|
2267
4563
|
}
|
|
4564
|
+
}), this.commands.set(".download", {
|
|
4565
|
+
name: ".download",
|
|
4566
|
+
description: "Download last result as file",
|
|
4567
|
+
usage: ".download [filename]",
|
|
4568
|
+
handler: (t) => this.cmdDownload(t)
|
|
2268
4569
|
}), this.commands.set(".highlight", {
|
|
2269
4570
|
name: ".highlight",
|
|
2270
4571
|
description: "Toggle syntax highlighting",
|
|
@@ -2289,6 +4590,24 @@ class te {
|
|
|
2289
4590
|
description: "Get or set the command prompt",
|
|
2290
4591
|
usage: ".prompt [primary [continuation]]",
|
|
2291
4592
|
handler: (t) => this.cmdPrompt(t)
|
|
4593
|
+
}), this.commands.set(".chart", {
|
|
4594
|
+
name: ".chart",
|
|
4595
|
+
description: "Show chart of last query result",
|
|
4596
|
+
usage: ".chart [type=line|bar|scatter|histogram] [x=col] [y=col,...]",
|
|
4597
|
+
handler: (t) => this.cmdChart(t)
|
|
4598
|
+
}), this.commands.set(".clearhistory", {
|
|
4599
|
+
name: ".clearhistory",
|
|
4600
|
+
description: "Clear command history",
|
|
4601
|
+
handler: () => this.cmdClearHistory()
|
|
4602
|
+
}), this.commands.set(".share", {
|
|
4603
|
+
name: ".share",
|
|
4604
|
+
description: "Open sharing modal to share queries",
|
|
4605
|
+
handler: () => this.openSharingModal()
|
|
4606
|
+
}), this.commands.set(".ai", {
|
|
4607
|
+
name: ".ai",
|
|
4608
|
+
description: "AI-powered natural language to SQL",
|
|
4609
|
+
usage: ".ai query <question> | .ai provider list|set <name> | .ai endpoint get|set <url>",
|
|
4610
|
+
handler: (t) => this.cmdAI(t)
|
|
2292
4611
|
});
|
|
2293
4612
|
}
|
|
2294
4613
|
/**
|
|
@@ -2298,7 +4617,7 @@ class te {
|
|
|
2298
4617
|
*/
|
|
2299
4618
|
showPrompt() {
|
|
2300
4619
|
const t = this.state === "collecting" ? this.continuationPrompt : this.prompt;
|
|
2301
|
-
this.inputBuffer.setPromptLength(
|
|
4620
|
+
this.inputBuffer.setPromptLength(ae(t)), this.write(d(t, b)), this.state !== "collecting" && this.setState("idle");
|
|
2302
4621
|
}
|
|
2303
4622
|
/**
|
|
2304
4623
|
* Handles terminal resize events.
|
|
@@ -2337,7 +4656,7 @@ class te {
|
|
|
2337
4656
|
o += `\r
|
|
2338
4657
|
`;
|
|
2339
4658
|
const a = this.state === "collecting" ? this.continuationPrompt : this.prompt;
|
|
2340
|
-
o +=
|
|
4659
|
+
o += d(a, b), o += i, n.row > r.row && (o += D(n.row - r.row)), o += E(r.col + 1), this.write(o);
|
|
2341
4660
|
}
|
|
2342
4661
|
/**
|
|
2343
4662
|
* Handles raw terminal input data.
|
|
@@ -2373,11 +4692,11 @@ class te {
|
|
|
2373
4692
|
async handlePaginationInput(t) {
|
|
2374
4693
|
const e = Math.ceil(this.totalRows / this.pageSize), i = t.toLowerCase();
|
|
2375
4694
|
if (i === "n" || t === "\x1B[B") {
|
|
2376
|
-
this.currentPage < e - 1 ? (this.currentPage++, await this.executePaginatedQuery()) : this.writeln(
|
|
4695
|
+
this.currentPage < e - 1 ? (this.currentPage++, await this.executePaginatedQuery()) : this.writeln(m("Already on last page"));
|
|
2377
4696
|
return;
|
|
2378
4697
|
}
|
|
2379
4698
|
if (i === "p" || t === "\x1B[A") {
|
|
2380
|
-
this.currentPage > 0 ? (this.currentPage--, await this.executePaginatedQuery()) : this.writeln(
|
|
4699
|
+
this.currentPage > 0 ? (this.currentPage--, await this.executePaginatedQuery()) : this.writeln(m("Already on first page"));
|
|
2381
4700
|
return;
|
|
2382
4701
|
}
|
|
2383
4702
|
if (i === "q" || i === "\x1B" || i === "") {
|
|
@@ -2389,7 +4708,7 @@ class te {
|
|
|
2389
4708
|
const r = this.inputBuffer.getContent().trim();
|
|
2390
4709
|
if (r) {
|
|
2391
4710
|
const n = parseInt(r, 10);
|
|
2392
|
-
!isNaN(n) && n >= 1 && n <= e ? (this.currentPage = n - 1, this.inputBuffer.clear(), await this.executePaginatedQuery()) : (this.writeln(
|
|
4711
|
+
!isNaN(n) && n >= 1 && n <= e ? (this.currentPage = n - 1, this.inputBuffer.clear(), await this.executePaginatedQuery()) : (this.writeln(d(`Invalid page number. Enter 1-${e}`, w)), this.inputBuffer.clear());
|
|
2393
4712
|
}
|
|
2394
4713
|
return;
|
|
2395
4714
|
}
|
|
@@ -2511,22 +4830,34 @@ class te {
|
|
|
2511
4830
|
}
|
|
2512
4831
|
this.collectedSQL.push(t);
|
|
2513
4832
|
const e = this.collectedSQL.join(`
|
|
2514
|
-
`).trim();
|
|
2515
|
-
|
|
4833
|
+
`).trim(), i = Te(e);
|
|
4834
|
+
if (i) {
|
|
4835
|
+
const r = e.replace(/\s*\n\s*/g, " ").trim();
|
|
4836
|
+
if (await this.history.add(r), this.database.isPoachedLoaded()) {
|
|
4837
|
+
const n = await this.database.validateSQL(e);
|
|
4838
|
+
if (n && !n.isValid && n.error?.exceptionMessage) {
|
|
4839
|
+
this.writeln(""), this.writeln(d("Error: ", w) + n.error.exceptionMessage), this.collectedSQL = [], this.setState("idle"), this.inputBuffer.clear(), this.writeln(""), this.showPrompt();
|
|
4840
|
+
return;
|
|
4841
|
+
}
|
|
4842
|
+
}
|
|
4843
|
+
this.writeln(""), await this.executeSQL(e), this.collectedSQL = [], this.state !== "paginating" && this.setState("idle");
|
|
4844
|
+
} else
|
|
4845
|
+
this.setState("collecting");
|
|
4846
|
+
this.inputBuffer.clear(), this.state !== "paginating" && (i && this.writeln(""), this.showPrompt());
|
|
2516
4847
|
}
|
|
2517
4848
|
/**
|
|
2518
4849
|
* Navigates to the previous command in history (Arrow Up).
|
|
2519
4850
|
*/
|
|
2520
4851
|
handleArrowUp() {
|
|
2521
4852
|
const t = this.history.previous(this.inputBuffer.getContent());
|
|
2522
|
-
t !== null && (this.write(this.inputBuffer.clearLine()), this.inputBuffer.setContent(t), this.write(this.
|
|
4853
|
+
t !== null && (this.write(this.inputBuffer.clearLine()), this.inputBuffer.setContent(t), this.write(t), this.syntaxHighlighting && this.redrawLineHighlighted());
|
|
2523
4854
|
}
|
|
2524
4855
|
/**
|
|
2525
4856
|
* Navigates to the next command in history (Arrow Down).
|
|
2526
4857
|
*/
|
|
2527
4858
|
handleArrowDown() {
|
|
2528
4859
|
const t = this.history.next();
|
|
2529
|
-
t !== null && (this.write(this.inputBuffer.clearLine()), this.inputBuffer.setContent(t), this.write(this.
|
|
4860
|
+
t !== null && (this.write(this.inputBuffer.clearLine()), this.inputBuffer.setContent(t), this.write(t), this.syntaxHighlighting && this.redrawLineHighlighted());
|
|
2530
4861
|
}
|
|
2531
4862
|
/**
|
|
2532
4863
|
* Handles Tab key for auto-completion.
|
|
@@ -2542,14 +4873,14 @@ class te {
|
|
|
2542
4873
|
else {
|
|
2543
4874
|
this.writeln("");
|
|
2544
4875
|
const r = i.map((l) => {
|
|
2545
|
-
const c = l.type === "keyword" ?
|
|
2546
|
-
return
|
|
4876
|
+
const c = l.type === "keyword" ? tt : l.type === "table" ? b : l.type === "function" ? F : et;
|
|
4877
|
+
return d(l.value, c);
|
|
2547
4878
|
}).join(" ");
|
|
2548
4879
|
this.writeln(r);
|
|
2549
4880
|
const n = this.findCommonPrefix(i.map((l) => l.value)), o = this.inputBuffer.getWordBeforeCursor();
|
|
2550
4881
|
n.length > o.length && this.write(this.inputBuffer.replaceWordBeforeCursor(n));
|
|
2551
4882
|
const a = this.state === "collecting" ? this.continuationPrompt : this.prompt;
|
|
2552
|
-
this.write(
|
|
4883
|
+
this.write(d(a, b)), this.write(this.inputBuffer.getContent()), this.syntaxHighlighting && this.redrawLineHighlighted();
|
|
2553
4884
|
}
|
|
2554
4885
|
}
|
|
2555
4886
|
/**
|
|
@@ -2581,7 +4912,7 @@ class te {
|
|
|
2581
4912
|
* Inserts text character-by-character, skipping newlines.
|
|
2582
4913
|
*/
|
|
2583
4914
|
async handlePaste() {
|
|
2584
|
-
const t = await
|
|
4915
|
+
const t = await xe();
|
|
2585
4916
|
if (t)
|
|
2586
4917
|
for (const e of t)
|
|
2587
4918
|
e === `
|
|
@@ -2592,23 +4923,57 @@ class te {
|
|
|
2592
4923
|
*/
|
|
2593
4924
|
async copyLastResult() {
|
|
2594
4925
|
if (!this.lastQueryResult)
|
|
2595
|
-
return this.writeln(
|
|
4926
|
+
return this.writeln(m("No query result to copy")), !1;
|
|
2596
4927
|
let t;
|
|
2597
4928
|
switch (this.outputMode) {
|
|
2598
4929
|
case "csv":
|
|
2599
|
-
t =
|
|
4930
|
+
t = Q(this.lastQueryResult.columns, this.lastQueryResult.rows);
|
|
2600
4931
|
break;
|
|
2601
4932
|
case "tsv":
|
|
2602
|
-
t =
|
|
4933
|
+
t = _(this.lastQueryResult.columns, this.lastQueryResult.rows);
|
|
2603
4934
|
break;
|
|
2604
4935
|
case "json":
|
|
2605
|
-
t =
|
|
4936
|
+
t = z(this.lastQueryResult.columns, this.lastQueryResult.rows);
|
|
2606
4937
|
break;
|
|
2607
4938
|
default:
|
|
2608
|
-
t =
|
|
4939
|
+
t = H(this.lastQueryResult.columns, this.lastQueryResult.rows);
|
|
4940
|
+
}
|
|
4941
|
+
const e = await st(t);
|
|
4942
|
+
return e ? this.writeln(d("Result copied to clipboard", b)) : this.writeln(d("Failed to copy to clipboard", w)), e;
|
|
4943
|
+
}
|
|
4944
|
+
/**
|
|
4945
|
+
* Download last query result as a file in the current output format.
|
|
4946
|
+
*/
|
|
4947
|
+
cmdDownload(t) {
|
|
4948
|
+
if (!this.lastQueryResult) {
|
|
4949
|
+
this.writeln(m("No query result to download"));
|
|
4950
|
+
return;
|
|
4951
|
+
}
|
|
4952
|
+
let e, i, r;
|
|
4953
|
+
switch (this.outputMode) {
|
|
4954
|
+
case "csv":
|
|
4955
|
+
e = Q(this.lastQueryResult.columns, this.lastQueryResult.rows), i = ".csv", r = "text/csv";
|
|
4956
|
+
break;
|
|
4957
|
+
case "tsv":
|
|
4958
|
+
e = _(this.lastQueryResult.columns, this.lastQueryResult.rows), i = ".tsv", r = "text/tab-separated-values";
|
|
4959
|
+
break;
|
|
4960
|
+
case "json":
|
|
4961
|
+
e = z(this.lastQueryResult.columns, this.lastQueryResult.rows), i = ".json", r = "application/json";
|
|
4962
|
+
break;
|
|
4963
|
+
default:
|
|
4964
|
+
e = H(this.lastQueryResult.columns, this.lastQueryResult.rows), i = ".txt", r = "text/plain";
|
|
2609
4965
|
}
|
|
2610
|
-
const
|
|
2611
|
-
|
|
4966
|
+
const n = this.generateDownloadFilename(t[0], i);
|
|
4967
|
+
be(e, n, r), this.writeln(d(`Downloaded: ${n}`, b));
|
|
4968
|
+
}
|
|
4969
|
+
/**
|
|
4970
|
+
* Generate a filename for download with timestamp or custom name.
|
|
4971
|
+
*/
|
|
4972
|
+
generateDownloadFilename(t, e) {
|
|
4973
|
+
if (t)
|
|
4974
|
+
return t.endsWith(e) ? t : t + e;
|
|
4975
|
+
const i = /* @__PURE__ */ new Date(), r = i.getFullYear(), n = String(i.getMonth() + 1).padStart(2, "0"), o = String(i.getDate()).padStart(2, "0"), a = String(i.getHours()).padStart(2, "0"), l = String(i.getMinutes()).padStart(2, "0"), c = String(i.getSeconds()).padStart(2, "0");
|
|
4976
|
+
return `duckdb-result-${r}-${n}-${o}-${a}${l}${c}${e}`;
|
|
2612
4977
|
}
|
|
2613
4978
|
/**
|
|
2614
4979
|
* Executes a dot command (e.g., .help, .tables, .schema).
|
|
@@ -2619,9 +4984,9 @@ class te {
|
|
|
2619
4984
|
* @param input - The full command string including arguments
|
|
2620
4985
|
*/
|
|
2621
4986
|
async executeCommand(t) {
|
|
2622
|
-
const { command: e, args: i } =
|
|
4987
|
+
const { command: e, args: i } = Le(t), r = this.commands.get(e);
|
|
2623
4988
|
if (!r) {
|
|
2624
|
-
this.writeln(
|
|
4989
|
+
this.writeln(d(`Unknown command: ${e}`, w)), this.writeln("Type .help for available commands"), this.emit("error", { message: `Unknown command: ${e}`, source: "command" });
|
|
2625
4990
|
return;
|
|
2626
4991
|
}
|
|
2627
4992
|
this.emit("commandExecute", { command: e, args: i });
|
|
@@ -2629,7 +4994,7 @@ class te {
|
|
|
2629
4994
|
await r.handler(i, this);
|
|
2630
4995
|
} catch (n) {
|
|
2631
4996
|
const o = n instanceof Error ? n.message : String(n);
|
|
2632
|
-
this.writeln(
|
|
4997
|
+
this.writeln(d(`Error: ${o}`, w)), this.emit("error", { message: o, source: "command" });
|
|
2633
4998
|
}
|
|
2634
4999
|
}
|
|
2635
5000
|
/**
|
|
@@ -2669,6 +5034,31 @@ class te {
|
|
|
2669
5034
|
const e = this.queryQueue.then(() => this.executeSQLInternal(t));
|
|
2670
5035
|
return this.queryQueue = e.catch(() => null), e;
|
|
2671
5036
|
}
|
|
5037
|
+
/**
|
|
5038
|
+
* Highlights SQL using DuckDB's tokenizer for syntax coloring.
|
|
5039
|
+
*
|
|
5040
|
+
* This method uses DuckDB's internal parser (via the poached extension) to
|
|
5041
|
+
* tokenize the SQL and apply ANSI color codes for keywords, strings, numbers,
|
|
5042
|
+
* functions, etc. The result can be written to the terminal for colored output.
|
|
5043
|
+
*
|
|
5044
|
+
* @param sql - The SQL string to highlight
|
|
5045
|
+
* @returns The highlighted SQL string with ANSI color codes, or the original
|
|
5046
|
+
* SQL if highlighting is disabled or tokenization fails
|
|
5047
|
+
*
|
|
5048
|
+
* @example Highlight and display a query
|
|
5049
|
+
* ```typescript
|
|
5050
|
+
* const highlighted = await terminal.highlightSQL('SELECT * FROM users;');
|
|
5051
|
+
* terminal.writeln(highlighted);
|
|
5052
|
+
* ```
|
|
5053
|
+
*/
|
|
5054
|
+
async highlightSQL(t) {
|
|
5055
|
+
if (this.database.isPoachedLoaded()) {
|
|
5056
|
+
const e = await this.database.tokenizeSQL(t);
|
|
5057
|
+
if (e)
|
|
5058
|
+
return K(t, e);
|
|
5059
|
+
}
|
|
5060
|
+
return t;
|
|
5061
|
+
}
|
|
2672
5062
|
/**
|
|
2673
5063
|
* Internal SQL execution logic.
|
|
2674
5064
|
*
|
|
@@ -2681,25 +5071,23 @@ class te {
|
|
|
2681
5071
|
const e = performance.now();
|
|
2682
5072
|
this.emit("queryStart", { sql: t });
|
|
2683
5073
|
try {
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
5074
|
+
const i = t.replace(/\s*\n\s*/g, " ").trim();
|
|
5075
|
+
await this.history.add(i);
|
|
5076
|
+
const r = t.trim().replace(/;+$/, ""), n = /^\s*SELECT\s/i.test(r), o = /\b(LIMIT|OFFSET)\b/i.test(r);
|
|
5077
|
+
if (this.pageSize > 0 && n && !o) {
|
|
5078
|
+
const c = `SELECT COUNT(*) as cnt FROM (${r}) AS _count_subquery`, u = (await this.database.executeQuery(c)).rows[0]?.[0], p = typeof u == "bigint" ? Number(u) : u ?? 0;
|
|
5079
|
+
if (p > this.pageSize)
|
|
5080
|
+
return this.paginationQuery = r, this.totalRows = p, this.currentPage = 0, await this.executePaginatedQuery();
|
|
2690
5081
|
}
|
|
2691
|
-
const
|
|
2692
|
-
return this.lastQueryResult =
|
|
2693
|
-
|
|
2694
|
-
), this.emit("queryEnd", { sql: t, result:
|
|
5082
|
+
const a = await this.database.executeQuery(t), l = performance.now() - e;
|
|
5083
|
+
return this.lastQueryResult = a, a.columns.length > 0 && this.displayResult(a), this.showTimer && this.writeln(
|
|
5084
|
+
m(`Time: ${a.duration.toFixed(2)}ms`)
|
|
5085
|
+
), this.emit("queryEnd", { sql: t, result: a, duration: l }), a;
|
|
2695
5086
|
} catch (i) {
|
|
2696
|
-
const r = i instanceof Error ? i.message : String(i), n = performance.now() - e
|
|
2697
|
-
|
|
2698
|
-
`) ||
|
|
2699
|
-
|
|
2700
|
-
this.writeln(d(` Query: ${o.replace(/\n/g, " ")}`));
|
|
2701
|
-
}
|
|
2702
|
-
return this.emit("queryEnd", { sql: t, result: null, error: r, duration: n }), this.emit("error", { message: r, source: "query" }), null;
|
|
5087
|
+
const r = i instanceof Error ? i.message : String(i), n = performance.now() - e, o = r.split(`
|
|
5088
|
+
|
|
5089
|
+
`)[0] || r;
|
|
5090
|
+
return this.writeln(d("Error: ", w) + o), this.emit("queryEnd", { sql: t, result: null, error: r, duration: n }), this.emit("error", { message: r, source: "query" }), null;
|
|
2703
5091
|
} finally {
|
|
2704
5092
|
this.state !== "paginating" && this.setState("idle");
|
|
2705
5093
|
}
|
|
@@ -2720,15 +5108,15 @@ class te {
|
|
|
2720
5108
|
this.lastQueryResult = i, i.columns.length > 0 && this.displayResult(i);
|
|
2721
5109
|
const r = Math.ceil(this.totalRows / this.pageSize), n = t + 1, o = Math.min(t + this.pageSize, this.totalRows);
|
|
2722
5110
|
return this.writeln(""), this.writeln(
|
|
2723
|
-
|
|
5111
|
+
m(`Showing rows ${n}-${o} of ${this.totalRows} (page ${this.currentPage + 1}/${r})`)
|
|
2724
5112
|
), this.writeln(
|
|
2725
|
-
|
|
5113
|
+
d(" [n]ext [p]rev [q]uit or enter page number", v)
|
|
2726
5114
|
), this.setState("paginating"), i;
|
|
2727
5115
|
} catch (i) {
|
|
2728
5116
|
const r = i instanceof Error ? i.message : String(i);
|
|
2729
|
-
if (this.writeln(
|
|
5117
|
+
if (this.writeln(d(`Error: ${r}`, w)), e.length > 80) {
|
|
2730
5118
|
const n = e.length > 200 ? e.substring(0, 200) + "..." : e;
|
|
2731
|
-
this.writeln(
|
|
5119
|
+
this.writeln(m(` Query: ${n.replace(/\n/g, " ")}`));
|
|
2732
5120
|
}
|
|
2733
5121
|
return this.exitPagination(), null;
|
|
2734
5122
|
}
|
|
@@ -2750,38 +5138,39 @@ class te {
|
|
|
2750
5138
|
displayResult(t) {
|
|
2751
5139
|
switch (this.outputMode) {
|
|
2752
5140
|
case "csv":
|
|
2753
|
-
this.writeln(
|
|
5141
|
+
this.writeln(Q(t.columns, t.rows));
|
|
2754
5142
|
break;
|
|
2755
5143
|
case "tsv":
|
|
2756
|
-
this.writeln(
|
|
5144
|
+
this.writeln(_(t.columns, t.rows));
|
|
2757
5145
|
break;
|
|
2758
5146
|
case "json":
|
|
2759
|
-
this.writeln(
|
|
5147
|
+
this.writeln(z(t.columns, t.rows));
|
|
2760
5148
|
break;
|
|
2761
5149
|
case "table":
|
|
2762
5150
|
default:
|
|
2763
|
-
this.writeln(
|
|
5151
|
+
this.writeln(H(t.columns, t.rows));
|
|
2764
5152
|
break;
|
|
2765
5153
|
}
|
|
2766
5154
|
this.writeln(
|
|
2767
|
-
|
|
5155
|
+
m(
|
|
2768
5156
|
`${t.rowCount} row${t.rowCount !== 1 ? "s" : ""}`
|
|
2769
5157
|
)
|
|
2770
5158
|
);
|
|
2771
5159
|
}
|
|
2772
5160
|
// Command handlers
|
|
2773
5161
|
cmdHelp() {
|
|
2774
|
-
this.writeln(
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
5162
|
+
this.writeln(L("Available commands:")), this.writeln("");
|
|
5163
|
+
const t = [...this.commands.values()].sort((e, i) => e.name.localeCompare(i.name));
|
|
5164
|
+
for (const e of t) {
|
|
5165
|
+
const i = e.usage ? ` ${m(e.usage)}` : "";
|
|
5166
|
+
this.writeln(` ${d(e.name, v)} ${e.description}${i}`);
|
|
2778
5167
|
}
|
|
2779
5168
|
this.writeln(""), this.writeln("SQL statements must end with a semicolon (;)");
|
|
2780
5169
|
}
|
|
2781
5170
|
async cmdTables() {
|
|
2782
5171
|
const t = await this.database.getTables();
|
|
2783
5172
|
if (t.length === 0) {
|
|
2784
|
-
this.writeln(
|
|
5173
|
+
this.writeln(m("No tables found"));
|
|
2785
5174
|
return;
|
|
2786
5175
|
}
|
|
2787
5176
|
for (const e of t)
|
|
@@ -2794,11 +5183,11 @@ class te {
|
|
|
2794
5183
|
}
|
|
2795
5184
|
const e = await this.database.getTableSchema(t[0]);
|
|
2796
5185
|
if (e.length === 0) {
|
|
2797
|
-
this.writeln(
|
|
5186
|
+
this.writeln(m(`Table not found: ${t[0]}`));
|
|
2798
5187
|
return;
|
|
2799
5188
|
}
|
|
2800
5189
|
for (const i of e)
|
|
2801
|
-
this.writeln(` ${i.name} ${
|
|
5190
|
+
this.writeln(` ${i.name} ${m(i.type)}`);
|
|
2802
5191
|
}
|
|
2803
5192
|
cmdTimer(t) {
|
|
2804
5193
|
if (t.length === 0) {
|
|
@@ -2825,20 +5214,20 @@ class te {
|
|
|
2825
5214
|
const e = t[0].toLowerCase();
|
|
2826
5215
|
e === "dark" || e === "light" ? this.setTheme(e) : this.writeln("Usage: .theme dark|light");
|
|
2827
5216
|
}
|
|
2828
|
-
cmdExamples() {
|
|
2829
|
-
this.writeln(
|
|
5217
|
+
async cmdExamples() {
|
|
5218
|
+
this.writeln(L("Example queries:")), this.writeln(""), this.writeln(d(" -- Create a table", T)), this.writeln(" " + await this.getHighlightedSQLAsync("CREATE TABLE users (id INTEGER, name VARCHAR);")), this.writeln(""), this.writeln(d(" -- Insert data", T)), this.writeln(" " + await this.getHighlightedSQLAsync("INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob');")), this.writeln(""), this.writeln(d(" -- Query data", T)), this.writeln(" " + await this.getHighlightedSQLAsync("SELECT * FROM users WHERE name LIKE 'A%';")), this.writeln(""), this.writeln(d(" -- Use built-in functions", T)), this.writeln(" " + await this.getHighlightedSQLAsync("SELECT range(10), current_timestamp;"));
|
|
2830
5219
|
}
|
|
2831
5220
|
async cmdFiles(t) {
|
|
2832
5221
|
const e = t[0]?.toLowerCase() ?? "list";
|
|
2833
5222
|
if (e === "list") {
|
|
2834
5223
|
if (this.loadedFiles.size === 0) {
|
|
2835
|
-
this.writeln(
|
|
5224
|
+
this.writeln(m("No files loaded")), this.writeln(m("Use .open or drag-and-drop to add files"));
|
|
2836
5225
|
return;
|
|
2837
5226
|
}
|
|
2838
|
-
this.writeln(
|
|
5227
|
+
this.writeln(L("Loaded files:"));
|
|
2839
5228
|
let i = 1;
|
|
2840
5229
|
for (const [r, n] of this.loadedFiles)
|
|
2841
|
-
this.writeln(` ${
|
|
5230
|
+
this.writeln(` ${m(`${i}.`)} ${r} ${m(`(${U(n.size)})`)}`), i++;
|
|
2842
5231
|
} else if (e === "add")
|
|
2843
5232
|
await this.cmdOpen();
|
|
2844
5233
|
else if (e === "remove" || e === "rm") {
|
|
@@ -2854,32 +5243,32 @@ class te {
|
|
|
2854
5243
|
if (r <= o.length)
|
|
2855
5244
|
n = o[r - 1];
|
|
2856
5245
|
else {
|
|
2857
|
-
this.writeln(
|
|
5246
|
+
this.writeln(d(`Invalid index: ${r}. Use .files list to see available files.`, "red"));
|
|
2858
5247
|
return;
|
|
2859
5248
|
}
|
|
2860
5249
|
} else if (this.loadedFiles.has(i))
|
|
2861
5250
|
n = i;
|
|
2862
5251
|
else {
|
|
2863
|
-
this.writeln(
|
|
5252
|
+
this.writeln(d(`File not found: ${i}`, "red"));
|
|
2864
5253
|
return;
|
|
2865
5254
|
}
|
|
2866
5255
|
if (n)
|
|
2867
5256
|
try {
|
|
2868
|
-
await this.database.dropFile(n), this.loadedFiles.delete(n), this.writeln(
|
|
5257
|
+
await this.database.dropFile(n), this.loadedFiles.delete(n), this.writeln(d(`Removed: ${n}`, "green"));
|
|
2869
5258
|
} catch (o) {
|
|
2870
|
-
this.writeln(
|
|
5259
|
+
this.writeln(d(`Error removing file: ${o}`, "red"));
|
|
2871
5260
|
}
|
|
2872
5261
|
} else
|
|
2873
5262
|
this.writeln("Usage: .files [list|add|remove <name|index>]");
|
|
2874
5263
|
}
|
|
2875
5264
|
async cmdOpen() {
|
|
2876
|
-
this.writeln(
|
|
2877
|
-
const t = await
|
|
5265
|
+
this.writeln(m("Opening file picker..."));
|
|
5266
|
+
const t = await Tt({
|
|
2878
5267
|
multiple: !0,
|
|
2879
5268
|
accept: ".csv,.parquet,.json,.db,.duckdb"
|
|
2880
5269
|
});
|
|
2881
5270
|
if (t.length === 0) {
|
|
2882
|
-
this.writeln(
|
|
5271
|
+
this.writeln(m("No files selected"));
|
|
2883
5272
|
return;
|
|
2884
5273
|
}
|
|
2885
5274
|
for (const e of t)
|
|
@@ -2924,15 +5313,15 @@ class te {
|
|
|
2924
5313
|
await this.database.dropFile(r);
|
|
2925
5314
|
} catch {
|
|
2926
5315
|
}
|
|
2927
|
-
this.loadedFiles.clear(), this.lastQueryResult = null, this.history.reset(), this.showTimer = !1, this.outputMode = "table", this.syntaxHighlighting = !0, this.pageSize = 0, this.linkProvider.setEnabled(!0), this.prompt =
|
|
2928
|
-
|
|
5316
|
+
this.loadedFiles.clear(), this.lastQueryResult = null, this.history.reset(), this.showTimer = !1, this.outputMode = "table", this.syntaxHighlighting = !0, this.pageSize = 0, this.linkProvider.setEnabled(!0), this.prompt = yt, this.continuationPrompt = bt, this.paginationQuery = null, this.currentPage = 0, this.totalRows = 0, this.writeln(
|
|
5317
|
+
d(
|
|
2929
5318
|
`Reset complete: dropped ${e} table${e !== 1 ? "s" : ""}, cleared ${i} file${i !== 1 ? "s" : ""}, settings restored to defaults`,
|
|
2930
|
-
|
|
5319
|
+
b
|
|
2931
5320
|
)
|
|
2932
5321
|
);
|
|
2933
5322
|
} catch (t) {
|
|
2934
5323
|
const e = t instanceof Error ? t.message : String(t);
|
|
2935
|
-
this.writeln(
|
|
5324
|
+
this.writeln(d(`Error during reset: ${e}`, w));
|
|
2936
5325
|
}
|
|
2937
5326
|
}
|
|
2938
5327
|
cmdPrompt(t) {
|
|
@@ -2943,6 +5332,29 @@ class te {
|
|
|
2943
5332
|
const e = t[0];
|
|
2944
5333
|
this.prompt = e, t.length >= 2 ? (this.continuationPrompt = t[1], this.writeln(`Prompts set to "${this.prompt}" and "${this.continuationPrompt}"`)) : this.writeln(`Primary prompt set to "${this.prompt}"`);
|
|
2945
5334
|
}
|
|
5335
|
+
async cmdClearHistory() {
|
|
5336
|
+
try {
|
|
5337
|
+
await this.history.clear(), this.writeln(d("Command history cleared", b));
|
|
5338
|
+
} catch (t) {
|
|
5339
|
+
const e = t instanceof Error ? t.message : String(t);
|
|
5340
|
+
this.writeln(d(`Error clearing history: ${e}`, w));
|
|
5341
|
+
}
|
|
5342
|
+
}
|
|
5343
|
+
async cmdChart(t) {
|
|
5344
|
+
if (!this.chartManager) {
|
|
5345
|
+
const r = this.resolveContainer(), n = this.getCurrentThemeObject();
|
|
5346
|
+
this.chartManager = new di(r, {
|
|
5347
|
+
enabled: this.config.enableCharts ?? !1,
|
|
5348
|
+
themeMode: this.getTheme(),
|
|
5349
|
+
themeColors: n.colors
|
|
5350
|
+
});
|
|
5351
|
+
}
|
|
5352
|
+
const e = t.length > 0 ? `.chart ${t.join(" ")}` : ".chart", i = await this.chartManager.executeCommand(
|
|
5353
|
+
e,
|
|
5354
|
+
this.lastQueryResult
|
|
5355
|
+
);
|
|
5356
|
+
i.success ? i.message && this.writeln(d(i.message, b)) : this.writeln(d(`Error: ${i.error}`, w));
|
|
5357
|
+
}
|
|
2946
5358
|
// ==================== TerminalInterface Implementation ====================
|
|
2947
5359
|
/**
|
|
2948
5360
|
* Writes text to the terminal without a trailing newline.
|
|
@@ -3027,9 +5439,9 @@ class te {
|
|
|
3027
5439
|
*/
|
|
3028
5440
|
setTheme(t) {
|
|
3029
5441
|
const e = this.getCurrentThemeObject();
|
|
3030
|
-
typeof t == "object" ? (this.customTheme = t, this.currentThemeName = "custom", this.terminalAdapter.setTheme(t), this.writeln(`Theme set to ${t.name}`)) : (this.customTheme = null, this.currentThemeName = t, this.terminalAdapter.setTheme(
|
|
5442
|
+
typeof t == "object" ? (this.customTheme = t, this.currentThemeName = "custom", this.terminalAdapter.setTheme(t), this.writeln(`Theme set to ${t.name}`)) : (this.customTheme = null, this.currentThemeName = t, this.terminalAdapter.setTheme(pt(t)), vi(t), this.writeln(`Theme set to ${t}`));
|
|
3031
5443
|
const i = this.getCurrentThemeObject();
|
|
3032
|
-
this.emit("themeChange", { theme: i, previous: e });
|
|
5444
|
+
this.emit("themeChange", { theme: i, previous: e }), this.chartManager && this.chartManager.setTheme(this.getTheme(), i.colors);
|
|
3033
5445
|
const r = this.currentThemeName === "light" || this.customTheme?.name === "light";
|
|
3034
5446
|
document.body.classList.toggle("light", r), document.body.classList.toggle("dark", !r);
|
|
3035
5447
|
}
|
|
@@ -3050,13 +5462,235 @@ class te {
|
|
|
3050
5462
|
getTheme() {
|
|
3051
5463
|
return this.currentThemeName === "custom" && this.customTheme ? this.customTheme.name === "light" ? "light" : "dark" : this.currentThemeName;
|
|
3052
5464
|
}
|
|
5465
|
+
/**
|
|
5466
|
+
* Displays the command prompt.
|
|
5467
|
+
*
|
|
5468
|
+
* Useful after programmatically executing SQL via {@link executeSQL} to
|
|
5469
|
+
* show the prompt for the next input. This is automatically called when
|
|
5470
|
+
* the user enters commands via the terminal, but needs to be called
|
|
5471
|
+
* manually when executing queries programmatically.
|
|
5472
|
+
*
|
|
5473
|
+
* @example
|
|
5474
|
+
* ```typescript
|
|
5475
|
+
* // Execute queries programmatically and show the prompt afterward
|
|
5476
|
+
* await terminal.executeSQL('CREATE TABLE users (id INT, name VARCHAR);');
|
|
5477
|
+
* await terminal.executeSQL("INSERT INTO users VALUES (1, 'Alice');");
|
|
5478
|
+
* terminal.refreshPrompt();
|
|
5479
|
+
* ```
|
|
5480
|
+
*/
|
|
5481
|
+
refreshPrompt() {
|
|
5482
|
+
this.state !== "paginating" && this.state !== "executing" && this.showPrompt();
|
|
5483
|
+
}
|
|
5484
|
+
// ==================== AI Commands ====================
|
|
5485
|
+
/**
|
|
5486
|
+
* Loads AI settings from localStorage on first use.
|
|
5487
|
+
*/
|
|
5488
|
+
loadAISettings() {
|
|
5489
|
+
if (!this.aiSettingsLoaded) {
|
|
5490
|
+
const t = Rt();
|
|
5491
|
+
this.aiEndpoint = t.endpoint, this.aiProvider = t.provider, this.aiSettingsLoaded = !0;
|
|
5492
|
+
}
|
|
5493
|
+
}
|
|
5494
|
+
/**
|
|
5495
|
+
* Main handler for the .ai command.
|
|
5496
|
+
* Routes to subcommand handlers based on the first argument.
|
|
5497
|
+
*/
|
|
5498
|
+
async cmdAI(t) {
|
|
5499
|
+
if (this.loadAISettings(), t.length === 0) {
|
|
5500
|
+
this.showAIHelp();
|
|
5501
|
+
return;
|
|
5502
|
+
}
|
|
5503
|
+
switch (t[0].toLowerCase()) {
|
|
5504
|
+
case "query":
|
|
5505
|
+
await this.cmdAIQuery(t.slice(1));
|
|
5506
|
+
break;
|
|
5507
|
+
case "provider":
|
|
5508
|
+
await this.cmdAIProvider(t.slice(1));
|
|
5509
|
+
break;
|
|
5510
|
+
case "endpoint":
|
|
5511
|
+
this.cmdAIEndpoint(t.slice(1));
|
|
5512
|
+
break;
|
|
5513
|
+
default:
|
|
5514
|
+
await this.cmdAIQuery(t);
|
|
5515
|
+
break;
|
|
5516
|
+
}
|
|
5517
|
+
}
|
|
5518
|
+
/**
|
|
5519
|
+
* Shows help for AI commands.
|
|
5520
|
+
*/
|
|
5521
|
+
showAIHelp() {
|
|
5522
|
+
this.writeln(L("AI Commands:")), this.writeln(""), this.writeln(` ${d(".ai <question>", v)}`), this.writeln(" Generate SQL from natural language question (shorthand)"), this.writeln(""), this.writeln(` ${d(".ai query <question>", v)}`), this.writeln(" Generate SQL from natural language question"), this.writeln(""), this.writeln(` ${d(".ai provider list", v)}`), this.writeln(" List available AI providers"), this.writeln(""), this.writeln(` ${d(".ai provider set <name>", v)}`), this.writeln(" Set the AI provider to use"), this.writeln(""), this.writeln(` ${d(".ai endpoint get", v)}`), this.writeln(" Show current proxy endpoint"), this.writeln(""), this.writeln(` ${d(".ai endpoint set <url>", v)}`), this.writeln(" Set the proxy endpoint URL");
|
|
5523
|
+
}
|
|
5524
|
+
/**
|
|
5525
|
+
* Shows the proxy unavailable error message.
|
|
5526
|
+
*/
|
|
5527
|
+
showProxyUnavailableError() {
|
|
5528
|
+
this.writeln(
|
|
5529
|
+
d(
|
|
5530
|
+
"Please install and run the Text-to-SQL Proxy, see https://github.com/tobilg/text-to-sql-proxy",
|
|
5531
|
+
w
|
|
5532
|
+
)
|
|
5533
|
+
);
|
|
5534
|
+
}
|
|
5535
|
+
/**
|
|
5536
|
+
* Handles the .ai query subcommand.
|
|
5537
|
+
* Generates SQL from a natural language question.
|
|
5538
|
+
*/
|
|
5539
|
+
async cmdAIQuery(t) {
|
|
5540
|
+
if (t.length === 0) {
|
|
5541
|
+
this.writeln(d("Error: Please provide a question", w)), this.writeln("Usage: .ai query <your question>");
|
|
5542
|
+
return;
|
|
5543
|
+
}
|
|
5544
|
+
const e = t.join(" ");
|
|
5545
|
+
if (!await X(this.aiEndpoint)) {
|
|
5546
|
+
this.showProxyUnavailableError();
|
|
5547
|
+
return;
|
|
5548
|
+
}
|
|
5549
|
+
const r = await this.database.getAllDDL();
|
|
5550
|
+
if (!r) {
|
|
5551
|
+
this.writeln(
|
|
5552
|
+
d(
|
|
5553
|
+
"Please create tables or views to be able to use the Text-to-SQL features",
|
|
5554
|
+
F
|
|
5555
|
+
)
|
|
5556
|
+
);
|
|
5557
|
+
return;
|
|
5558
|
+
}
|
|
5559
|
+
this.writeln(m("Generating SQL..."));
|
|
5560
|
+
try {
|
|
5561
|
+
const n = await Ei(
|
|
5562
|
+
this.aiEndpoint,
|
|
5563
|
+
r,
|
|
5564
|
+
e,
|
|
5565
|
+
this.aiProvider
|
|
5566
|
+
), o = n.trim().split(`
|
|
5567
|
+
`);
|
|
5568
|
+
this.writeln("");
|
|
5569
|
+
for (let l = 0; l < o.length; l++) {
|
|
5570
|
+
const c = o[l], f = l === 0 ? this.prompt : this.continuationPrompt;
|
|
5571
|
+
this.write(f);
|
|
5572
|
+
const u = await this.getHighlightedSQLAsync(c);
|
|
5573
|
+
this.write(u), this.writeln("");
|
|
5574
|
+
}
|
|
5575
|
+
const a = n.replace(/\s*\n\s*/g, " ").replace(/\s+/g, " ").trim();
|
|
5576
|
+
await this.history.add(a), this.writeln(""), await this.executeSQLInternal(n.trim());
|
|
5577
|
+
} catch (n) {
|
|
5578
|
+
n instanceof P ? this.writeln(d(`Error: ${n.message}`, w)) : this.writeln(
|
|
5579
|
+
d(
|
|
5580
|
+
`Error: ${n instanceof Error ? n.message : "Unknown error"}`,
|
|
5581
|
+
w
|
|
5582
|
+
)
|
|
5583
|
+
);
|
|
5584
|
+
}
|
|
5585
|
+
}
|
|
5586
|
+
/**
|
|
5587
|
+
* Handles the .ai provider subcommand.
|
|
5588
|
+
* Lists or sets the AI provider.
|
|
5589
|
+
*/
|
|
5590
|
+
async cmdAIProvider(t) {
|
|
5591
|
+
if (t.length === 0) {
|
|
5592
|
+
this.writeln(`Current provider: ${this.aiProvider}`);
|
|
5593
|
+
return;
|
|
5594
|
+
}
|
|
5595
|
+
const e = t[0].toLowerCase();
|
|
5596
|
+
if (e === "list") {
|
|
5597
|
+
if (!await X(this.aiEndpoint)) {
|
|
5598
|
+
this.showProxyUnavailableError();
|
|
5599
|
+
return;
|
|
5600
|
+
}
|
|
5601
|
+
this.writeln(m("Fetching providers..."));
|
|
5602
|
+
try {
|
|
5603
|
+
const r = await wt(this.aiEndpoint);
|
|
5604
|
+
if (r.length === 0) {
|
|
5605
|
+
this.writeln(m("No providers available"));
|
|
5606
|
+
return;
|
|
5607
|
+
}
|
|
5608
|
+
this.writeln(""), this.writeln(L("Available providers:"));
|
|
5609
|
+
for (const n of r) {
|
|
5610
|
+
const a = n.name === this.aiProvider ? d(" (current)", b) : "";
|
|
5611
|
+
this.writeln(` ${d(n.name, v)}${a}`), n.description && this.writeln(` ${m(n.description)}`);
|
|
5612
|
+
}
|
|
5613
|
+
} catch (r) {
|
|
5614
|
+
r instanceof P ? this.writeln(d(`Error: ${r.message}`, w)) : this.writeln(
|
|
5615
|
+
d(
|
|
5616
|
+
`Error: ${r instanceof Error ? r.message : "Unknown error"}`,
|
|
5617
|
+
w
|
|
5618
|
+
)
|
|
5619
|
+
);
|
|
5620
|
+
}
|
|
5621
|
+
} else if (e === "set") {
|
|
5622
|
+
const i = t.slice(1).join(" ");
|
|
5623
|
+
if (!i) {
|
|
5624
|
+
this.writeln(d("Error: Please provide a provider name", w)), this.writeln("Usage: .ai provider set <name>");
|
|
5625
|
+
return;
|
|
5626
|
+
}
|
|
5627
|
+
if (!await X(this.aiEndpoint)) {
|
|
5628
|
+
this.showProxyUnavailableError();
|
|
5629
|
+
return;
|
|
5630
|
+
}
|
|
5631
|
+
try {
|
|
5632
|
+
const o = (await wt(this.aiEndpoint)).find(
|
|
5633
|
+
(a) => a.name.toLowerCase() === i.toLowerCase()
|
|
5634
|
+
);
|
|
5635
|
+
if (!o) {
|
|
5636
|
+
this.writeln(d(`Error: Provider "${i}" not found`, w)), this.writeln("Use .ai provider list to see available providers");
|
|
5637
|
+
return;
|
|
5638
|
+
}
|
|
5639
|
+
this.aiProvider = o.name, O({ provider: this.aiProvider }), this.writeln(d(`Provider set to: ${this.aiProvider}`, b));
|
|
5640
|
+
} catch (n) {
|
|
5641
|
+
n instanceof P ? this.writeln(d(`Error: ${n.message}`, w)) : this.writeln(
|
|
5642
|
+
d(
|
|
5643
|
+
`Error: ${n instanceof Error ? n.message : "Unknown error"}`,
|
|
5644
|
+
w
|
|
5645
|
+
)
|
|
5646
|
+
);
|
|
5647
|
+
}
|
|
5648
|
+
} else if (e === "clear" || e === "reset") {
|
|
5649
|
+
const i = Ti();
|
|
5650
|
+
this.aiProvider = i, O({ provider: i }), this.writeln(d(`Provider reset to default: ${i}`, b));
|
|
5651
|
+
} else
|
|
5652
|
+
this.writeln(d(`Unknown action: ${e}`, w)), this.writeln("Usage: .ai provider list|set <name>|clear");
|
|
5653
|
+
}
|
|
5654
|
+
/**
|
|
5655
|
+
* Handles the .ai endpoint subcommand.
|
|
5656
|
+
* Gets or sets the proxy endpoint URL.
|
|
5657
|
+
*/
|
|
5658
|
+
cmdAIEndpoint(t) {
|
|
5659
|
+
if (t.length === 0) {
|
|
5660
|
+
this.writeln(`Current endpoint: ${this.aiEndpoint}`);
|
|
5661
|
+
return;
|
|
5662
|
+
}
|
|
5663
|
+
const e = t[0].toLowerCase();
|
|
5664
|
+
if (e === "get") {
|
|
5665
|
+
this.writeln(`Endpoint: ${this.aiEndpoint}`);
|
|
5666
|
+
const i = gt();
|
|
5667
|
+
this.aiEndpoint !== i && this.writeln(m(`Default: ${i}`));
|
|
5668
|
+
} else if (e === "set") {
|
|
5669
|
+
const i = t.slice(1).join(" ");
|
|
5670
|
+
if (!i) {
|
|
5671
|
+
this.writeln(d("Error: Please provide an endpoint URL", w)), this.writeln("Usage: .ai endpoint set <url>");
|
|
5672
|
+
return;
|
|
5673
|
+
}
|
|
5674
|
+
try {
|
|
5675
|
+
new URL(i);
|
|
5676
|
+
} catch {
|
|
5677
|
+
this.writeln(d("Error: Invalid URL format", w));
|
|
5678
|
+
return;
|
|
5679
|
+
}
|
|
5680
|
+
this.aiEndpoint = i, O({ endpoint: i }), this.writeln(d(`Endpoint set to: ${i}`, b));
|
|
5681
|
+
} else if (e === "reset") {
|
|
5682
|
+
const i = gt();
|
|
5683
|
+
this.aiEndpoint = i, O({ endpoint: i }), this.writeln(d(`Endpoint reset to: ${i}`, b));
|
|
5684
|
+
} else
|
|
5685
|
+
this.writeln(d(`Unknown action: ${e}`, w)), this.writeln("Usage: .ai endpoint get|set <url>|reset");
|
|
5686
|
+
}
|
|
3053
5687
|
}
|
|
3054
|
-
function
|
|
5688
|
+
function Ji(s) {
|
|
3055
5689
|
const t = /* @__PURE__ */ new Map();
|
|
3056
5690
|
return t.set(".help", {
|
|
3057
5691
|
name: ".help",
|
|
3058
5692
|
description: "Show available commands",
|
|
3059
|
-
handler: () =>
|
|
5693
|
+
handler: () => Li(t, s)
|
|
3060
5694
|
}), t.set(".clear", {
|
|
3061
5695
|
name: ".clear",
|
|
3062
5696
|
description: "Clear the terminal",
|
|
@@ -3064,101 +5698,101 @@ function Pe(s) {
|
|
|
3064
5698
|
}), t.set(".tables", {
|
|
3065
5699
|
name: ".tables",
|
|
3066
5700
|
description: "List all tables",
|
|
3067
|
-
handler: () =>
|
|
5701
|
+
handler: () => Pi(s)
|
|
3068
5702
|
}), t.set(".schema", {
|
|
3069
5703
|
name: ".schema",
|
|
3070
5704
|
description: "Show table schema",
|
|
3071
5705
|
usage: ".schema <table_name>",
|
|
3072
|
-
handler: (e) =>
|
|
5706
|
+
handler: (e) => ki(e, s)
|
|
3073
5707
|
}), t.set(".timer", {
|
|
3074
5708
|
name: ".timer",
|
|
3075
5709
|
description: "Toggle query timing",
|
|
3076
5710
|
usage: ".timer on|off",
|
|
3077
|
-
handler: (e) =>
|
|
5711
|
+
handler: (e) => Ii(e, s)
|
|
3078
5712
|
}), t.set(".mode", {
|
|
3079
5713
|
name: ".mode",
|
|
3080
5714
|
description: "Set output mode",
|
|
3081
5715
|
usage: ".mode table|csv|tsv|json",
|
|
3082
|
-
handler: (e) =>
|
|
5716
|
+
handler: (e) => $i(e, s)
|
|
3083
5717
|
}), t.set(".theme", {
|
|
3084
5718
|
name: ".theme",
|
|
3085
5719
|
description: "Set color theme (clears screen)",
|
|
3086
5720
|
usage: ".theme dark|light",
|
|
3087
|
-
handler: (e) =>
|
|
5721
|
+
handler: (e) => Ai(e, s)
|
|
3088
5722
|
}), t.set(".examples", {
|
|
3089
5723
|
name: ".examples",
|
|
3090
5724
|
description: "Show example queries",
|
|
3091
|
-
handler: () =>
|
|
5725
|
+
handler: () => Ri(s)
|
|
3092
5726
|
}), t.set(".files", {
|
|
3093
5727
|
name: ".files",
|
|
3094
5728
|
description: "Manage loaded files",
|
|
3095
5729
|
usage: ".files [list|add|remove <name|index>]",
|
|
3096
|
-
handler: (e) =>
|
|
5730
|
+
handler: (e) => Ni(e, s)
|
|
3097
5731
|
}), t.set(".open", {
|
|
3098
5732
|
name: ".open",
|
|
3099
5733
|
description: "Open a file picker to load files",
|
|
3100
|
-
handler: () =>
|
|
5734
|
+
handler: () => Nt(s)
|
|
3101
5735
|
}), t.set(".copy", {
|
|
3102
5736
|
name: ".copy",
|
|
3103
5737
|
description: "Copy last result to clipboard",
|
|
3104
|
-
handler: () =>
|
|
5738
|
+
handler: () => Fi(s)
|
|
3105
5739
|
}), t.set(".highlight", {
|
|
3106
5740
|
name: ".highlight",
|
|
3107
5741
|
description: "Toggle syntax highlighting",
|
|
3108
5742
|
usage: ".highlight on|off",
|
|
3109
|
-
handler: (e) =>
|
|
5743
|
+
handler: (e) => Mi(e, s)
|
|
3110
5744
|
}), t.set(".links", {
|
|
3111
5745
|
name: ".links",
|
|
3112
5746
|
description: "Toggle clickable URL detection",
|
|
3113
5747
|
usage: ".links on|off",
|
|
3114
|
-
handler: (e) =>
|
|
5748
|
+
handler: (e) => Bi(e, s)
|
|
3115
5749
|
}), t.set(".pagesize", {
|
|
3116
5750
|
name: ".pagesize",
|
|
3117
5751
|
description: "Enable pagination for large results (default: off)",
|
|
3118
5752
|
usage: ".pagesize <number> (0 = disabled)",
|
|
3119
|
-
handler: (e) =>
|
|
5753
|
+
handler: (e) => Di(e, s)
|
|
3120
5754
|
}), t.set(".reset", {
|
|
3121
5755
|
name: ".reset",
|
|
3122
5756
|
description: "Reset database and all settings to defaults",
|
|
3123
|
-
handler: () =>
|
|
5757
|
+
handler: () => Ui(s)
|
|
3124
5758
|
}), t.set(".prompt", {
|
|
3125
5759
|
name: ".prompt",
|
|
3126
5760
|
description: "Get or set the command prompt",
|
|
3127
5761
|
usage: ".prompt [primary [continuation]]",
|
|
3128
|
-
handler: (e) =>
|
|
5762
|
+
handler: (e) => Oi(e, s)
|
|
3129
5763
|
}), t;
|
|
3130
5764
|
}
|
|
3131
|
-
function
|
|
3132
|
-
t.writeln(
|
|
5765
|
+
function Li(s, t) {
|
|
5766
|
+
t.writeln(L("Available commands:")), t.writeln("");
|
|
3133
5767
|
for (const e of s.values()) {
|
|
3134
|
-
const i = e.usage ? ` ${
|
|
3135
|
-
t.writeln(` ${
|
|
5768
|
+
const i = e.usage ? ` ${m(e.usage)}` : "";
|
|
5769
|
+
t.writeln(` ${d(e.name, v)} ${e.description}${i}`);
|
|
3136
5770
|
}
|
|
3137
5771
|
t.writeln(""), t.writeln("SQL statements must end with a semicolon (;)");
|
|
3138
5772
|
}
|
|
3139
|
-
async function
|
|
5773
|
+
async function Pi(s) {
|
|
3140
5774
|
const t = await s.getDatabase().getTables();
|
|
3141
5775
|
if (t.length === 0) {
|
|
3142
|
-
s.writeln(
|
|
5776
|
+
s.writeln(m("No tables found"));
|
|
3143
5777
|
return;
|
|
3144
5778
|
}
|
|
3145
5779
|
for (const e of t)
|
|
3146
5780
|
s.writeln(e);
|
|
3147
5781
|
}
|
|
3148
|
-
async function
|
|
5782
|
+
async function ki(s, t) {
|
|
3149
5783
|
if (s.length === 0) {
|
|
3150
5784
|
t.writeln("Usage: .schema <table_name>");
|
|
3151
5785
|
return;
|
|
3152
5786
|
}
|
|
3153
5787
|
const e = await t.getDatabase().getTableSchema(s[0]);
|
|
3154
5788
|
if (e.length === 0) {
|
|
3155
|
-
t.writeln(
|
|
5789
|
+
t.writeln(m(`Table not found: ${s[0]}`));
|
|
3156
5790
|
return;
|
|
3157
5791
|
}
|
|
3158
5792
|
for (const i of e)
|
|
3159
|
-
t.writeln(` ${i.name} ${
|
|
5793
|
+
t.writeln(` ${i.name} ${m(i.type)}`);
|
|
3160
5794
|
}
|
|
3161
|
-
function
|
|
5795
|
+
function Ii(s, t) {
|
|
3162
5796
|
if (s.length === 0) {
|
|
3163
5797
|
t.writeln(`Timer is ${t.getShowTimer() ? "on" : "off"}`);
|
|
3164
5798
|
return;
|
|
@@ -3166,7 +5800,7 @@ function re(s, t) {
|
|
|
3166
5800
|
const e = s[0].toLowerCase();
|
|
3167
5801
|
e === "on" ? (t.setShowTimer(!0), t.writeln("Timer is now on")) : e === "off" ? (t.setShowTimer(!1), t.writeln("Timer is now off")) : t.writeln("Usage: .timer on|off");
|
|
3168
5802
|
}
|
|
3169
|
-
function
|
|
5803
|
+
function $i(s, t) {
|
|
3170
5804
|
if (s.length === 0) {
|
|
3171
5805
|
t.writeln(`Output mode: ${t.getOutputMode()}`);
|
|
3172
5806
|
return;
|
|
@@ -3174,7 +5808,7 @@ function ne(s, t) {
|
|
|
3174
5808
|
const e = s[0].toLowerCase();
|
|
3175
5809
|
e === "table" || e === "csv" || e === "tsv" || e === "json" ? (t.setOutputMode(e), t.writeln(`Output mode set to ${e}`)) : t.writeln("Usage: .mode table|csv|tsv|json");
|
|
3176
5810
|
}
|
|
3177
|
-
function
|
|
5811
|
+
function Ai(s, t) {
|
|
3178
5812
|
if (s.length === 0) {
|
|
3179
5813
|
t.writeln(`Theme: ${t.getThemeName()}`);
|
|
3180
5814
|
return;
|
|
@@ -3182,22 +5816,22 @@ function oe(s, t) {
|
|
|
3182
5816
|
const e = s[0].toLowerCase();
|
|
3183
5817
|
e === "dark" || e === "light" ? t.setTheme(e) : t.writeln("Usage: .theme dark|light");
|
|
3184
5818
|
}
|
|
3185
|
-
function
|
|
3186
|
-
s.writeln(
|
|
5819
|
+
function Ri(s) {
|
|
5820
|
+
s.writeln(L("Example queries:")), s.writeln(""), s.writeln(d(" -- Create a table", T)), s.writeln(" " + s.getHighlightedSQL("CREATE TABLE users (id INTEGER, name VARCHAR);")), s.writeln(""), s.writeln(d(" -- Insert data", T)), s.writeln(" " + s.getHighlightedSQL("INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob');")), s.writeln(""), s.writeln(d(" -- Query data", T)), s.writeln(" " + s.getHighlightedSQL("SELECT * FROM users WHERE name LIKE 'A%';")), s.writeln(""), s.writeln(d(" -- Use built-in functions", T)), s.writeln(" " + s.getHighlightedSQL("SELECT range(10), current_timestamp;"));
|
|
3187
5821
|
}
|
|
3188
|
-
async function
|
|
5822
|
+
async function Ni(s, t) {
|
|
3189
5823
|
const e = s[0]?.toLowerCase() ?? "list", i = t.getLoadedFiles();
|
|
3190
5824
|
if (e === "list") {
|
|
3191
5825
|
if (i.size === 0) {
|
|
3192
|
-
t.writeln(
|
|
5826
|
+
t.writeln(m("No files loaded")), t.writeln(m("Use .open or drag-and-drop to add files"));
|
|
3193
5827
|
return;
|
|
3194
5828
|
}
|
|
3195
|
-
t.writeln(
|
|
5829
|
+
t.writeln(L("Loaded files:"));
|
|
3196
5830
|
let r = 1;
|
|
3197
5831
|
for (const [n, o] of i)
|
|
3198
|
-
t.writeln(` ${
|
|
5832
|
+
t.writeln(` ${m(`${r}.`)} ${n} ${m(`(${U(o.size)})`)}`), r++;
|
|
3199
5833
|
} else if (e === "add")
|
|
3200
|
-
await
|
|
5834
|
+
await Nt(t);
|
|
3201
5835
|
else if (e === "remove" || e === "rm") {
|
|
3202
5836
|
const r = s.slice(1).join(" ");
|
|
3203
5837
|
if (!r) {
|
|
@@ -3211,38 +5845,38 @@ async function le(s, t) {
|
|
|
3211
5845
|
if (n <= a.length)
|
|
3212
5846
|
o = a[n - 1];
|
|
3213
5847
|
else {
|
|
3214
|
-
t.writeln(
|
|
5848
|
+
t.writeln(d(`Invalid index: ${n}. Use .files list to see available files.`, "red"));
|
|
3215
5849
|
return;
|
|
3216
5850
|
}
|
|
3217
5851
|
} else if (i.has(r))
|
|
3218
5852
|
o = r;
|
|
3219
5853
|
else {
|
|
3220
|
-
t.writeln(
|
|
5854
|
+
t.writeln(d(`File not found: ${r}`, "red"));
|
|
3221
5855
|
return;
|
|
3222
5856
|
}
|
|
3223
5857
|
if (o)
|
|
3224
5858
|
try {
|
|
3225
|
-
await t.removeFile(o), t.writeln(
|
|
5859
|
+
await t.removeFile(o), t.writeln(d(`Removed: ${o}`, "green"));
|
|
3226
5860
|
} catch (a) {
|
|
3227
|
-
t.writeln(
|
|
5861
|
+
t.writeln(d(`Error removing file: ${a}`, "red"));
|
|
3228
5862
|
}
|
|
3229
5863
|
} else
|
|
3230
5864
|
t.writeln("Usage: .files [list|add|remove <name|index>]");
|
|
3231
5865
|
}
|
|
3232
|
-
async function
|
|
3233
|
-
s.writeln(
|
|
3234
|
-
const t = await
|
|
5866
|
+
async function Nt(s) {
|
|
5867
|
+
s.writeln(m("Opening file picker..."));
|
|
5868
|
+
const t = await Tt({
|
|
3235
5869
|
multiple: !0,
|
|
3236
5870
|
accept: ".csv,.parquet,.json,.db,.duckdb"
|
|
3237
5871
|
});
|
|
3238
5872
|
if (t.length === 0) {
|
|
3239
|
-
s.writeln(
|
|
5873
|
+
s.writeln(m("No files selected"));
|
|
3240
5874
|
return;
|
|
3241
5875
|
}
|
|
3242
5876
|
for (const e of t)
|
|
3243
5877
|
await s.loadFile(e);
|
|
3244
5878
|
}
|
|
3245
|
-
function
|
|
5879
|
+
function Mi(s, t) {
|
|
3246
5880
|
if (s.length === 0) {
|
|
3247
5881
|
t.writeln(`Syntax highlighting is ${t.getSyntaxHighlighting() ? "on" : "off"}`);
|
|
3248
5882
|
return;
|
|
@@ -3250,7 +5884,7 @@ function he(s, t) {
|
|
|
3250
5884
|
const e = s[0].toLowerCase();
|
|
3251
5885
|
e === "on" ? (t.setSyntaxHighlighting(!0), t.writeln("Syntax highlighting is now on")) : e === "off" ? (t.setSyntaxHighlighting(!1), t.writeln("Syntax highlighting is now off")) : t.writeln("Usage: .highlight on|off");
|
|
3252
5886
|
}
|
|
3253
|
-
function
|
|
5887
|
+
function Bi(s, t) {
|
|
3254
5888
|
const e = t.getLinkProvider();
|
|
3255
5889
|
if (s.length === 0) {
|
|
3256
5890
|
t.writeln(`URL link detection is ${e.isEnabled() ? "on" : "off"}`);
|
|
@@ -3259,7 +5893,7 @@ function ce(s, t) {
|
|
|
3259
5893
|
const i = s[0].toLowerCase();
|
|
3260
5894
|
i === "on" ? (e.setEnabled(!0), t.writeln("URL link detection is now on")) : i === "off" ? (e.setEnabled(!1), t.writeln("URL link detection is now off")) : t.writeln("Usage: .links on|off");
|
|
3261
5895
|
}
|
|
3262
|
-
function
|
|
5896
|
+
function Di(s, t) {
|
|
3263
5897
|
if (s.length === 0) {
|
|
3264
5898
|
const i = t.getPageSize();
|
|
3265
5899
|
i === 0 ? t.writeln("Pagination is disabled (showing all rows)") : t.writeln(`Page size: ${i} rows`);
|
|
@@ -3272,10 +5906,10 @@ function ue(s, t) {
|
|
|
3272
5906
|
}
|
|
3273
5907
|
t.setPageSize(e), e === 0 ? t.writeln("Pagination disabled (will show all rows)") : t.writeln(`Page size set to ${e} rows`);
|
|
3274
5908
|
}
|
|
3275
|
-
async function
|
|
5909
|
+
async function Ui(s) {
|
|
3276
5910
|
await s.resetState();
|
|
3277
5911
|
}
|
|
3278
|
-
function
|
|
5912
|
+
function Oi(s, t) {
|
|
3279
5913
|
if (s.length === 0) {
|
|
3280
5914
|
t.writeln(`Primary prompt: "${t.getPrompt()}"`), t.writeln(`Continuation prompt: "${t.getContinuationPrompt()}"`);
|
|
3281
5915
|
return;
|
|
@@ -3283,30 +5917,30 @@ function fe(s, t) {
|
|
|
3283
5917
|
const e = s[0];
|
|
3284
5918
|
s.length >= 2 ? (t.setPrompts(e, s[1]), t.writeln(`Prompts set to "${e}" and "${s[1]}"`)) : (t.setPrompts(e), t.writeln(`Primary prompt set to "${e}"`));
|
|
3285
5919
|
}
|
|
3286
|
-
async function
|
|
5920
|
+
async function Fi(s) {
|
|
3287
5921
|
const t = s.getLastQueryResult();
|
|
3288
5922
|
if (!t) {
|
|
3289
|
-
s.writeln(
|
|
5923
|
+
s.writeln(m("No query result to copy"));
|
|
3290
5924
|
return;
|
|
3291
5925
|
}
|
|
3292
5926
|
const e = s.getOutputMode();
|
|
3293
5927
|
let i;
|
|
3294
5928
|
switch (e) {
|
|
3295
5929
|
case "csv":
|
|
3296
|
-
i =
|
|
5930
|
+
i = Q(t.columns, t.rows);
|
|
3297
5931
|
break;
|
|
3298
5932
|
case "tsv":
|
|
3299
|
-
i =
|
|
5933
|
+
i = _(t.columns, t.rows);
|
|
3300
5934
|
break;
|
|
3301
5935
|
case "json":
|
|
3302
|
-
i =
|
|
5936
|
+
i = z(t.columns, t.rows);
|
|
3303
5937
|
break;
|
|
3304
5938
|
default:
|
|
3305
|
-
i =
|
|
5939
|
+
i = H(t.columns, t.rows);
|
|
3306
5940
|
}
|
|
3307
|
-
await
|
|
5941
|
+
await st(i) ? s.writeln(d(`Copied ${t.rowCount} rows to clipboard (${e} format)`, b)) : s.writeln(d("Failed to copy to clipboard", w));
|
|
3308
5942
|
}
|
|
3309
|
-
class
|
|
5943
|
+
class Zi {
|
|
3310
5944
|
constructor(t) {
|
|
3311
5945
|
h(this, "state", {
|
|
3312
5946
|
query: null,
|
|
@@ -3377,7 +6011,7 @@ class Ce {
|
|
|
3377
6011
|
this.ctx.displayResult(i, !1), this.showNavigationHint();
|
|
3378
6012
|
} catch (i) {
|
|
3379
6013
|
const r = i instanceof Error ? i.message : String(i);
|
|
3380
|
-
this.ctx.writeln(
|
|
6014
|
+
this.ctx.writeln(d(`Error: ${r}`, w));
|
|
3381
6015
|
}
|
|
3382
6016
|
}
|
|
3383
6017
|
/**
|
|
@@ -3386,7 +6020,7 @@ class Ce {
|
|
|
3386
6020
|
showNavigationHint() {
|
|
3387
6021
|
const t = this.getTotalPages();
|
|
3388
6022
|
this.ctx.writeln(""), this.ctx.writeln(
|
|
3389
|
-
|
|
6023
|
+
m(
|
|
3390
6024
|
`Page ${this.getCurrentPageDisplay()}/${t} (${this.state.totalRows} rows) - n:next p:prev 1-${t}:goto q:quit`
|
|
3391
6025
|
)
|
|
3392
6026
|
);
|
|
@@ -3399,9 +6033,9 @@ class Ce {
|
|
|
3399
6033
|
if (!this.state.isActive) return !1;
|
|
3400
6034
|
const e = this.getTotalPages(), i = t.toLowerCase();
|
|
3401
6035
|
if (i === "n" || t === "\x1B[B")
|
|
3402
|
-
return this.state.currentPage < e - 1 ? (this.state.currentPage++, await this.executeCurrentPage()) : this.ctx.writeln(
|
|
6036
|
+
return this.state.currentPage < e - 1 ? (this.state.currentPage++, await this.executeCurrentPage()) : this.ctx.writeln(m("Already on last page")), !0;
|
|
3403
6037
|
if (i === "p" || t === "\x1B[A")
|
|
3404
|
-
return this.state.currentPage > 0 ? (this.state.currentPage--, await this.executeCurrentPage()) : this.ctx.writeln(
|
|
6038
|
+
return this.state.currentPage > 0 ? (this.state.currentPage--, await this.executeCurrentPage()) : this.ctx.writeln(m("Already on first page")), !0;
|
|
3405
6039
|
if (i === "q" || i === "\x1B" || i === "")
|
|
3406
6040
|
return this.ctx.writeln(""), this.exit(), !0;
|
|
3407
6041
|
if (i === "\r" || i === `
|
|
@@ -3409,7 +6043,7 @@ class Ce {
|
|
|
3409
6043
|
const r = this.ctx.getInputContent().trim();
|
|
3410
6044
|
if (r) {
|
|
3411
6045
|
const n = parseInt(r, 10);
|
|
3412
|
-
!isNaN(n) && n >= 1 && n <= e ? (this.state.currentPage = n - 1, this.ctx.clearInput(), await this.executeCurrentPage()) : (this.ctx.writeln(
|
|
6046
|
+
!isNaN(n) && n >= 1 && n <= e ? (this.state.currentPage = n - 1, this.ctx.clearInput(), await this.executeCurrentPage()) : (this.ctx.writeln(d(`Invalid page number. Enter 1-${e}`, w)), this.ctx.clearInput());
|
|
3413
6047
|
}
|
|
3414
6048
|
return !0;
|
|
3415
6049
|
}
|
|
@@ -3432,52 +6066,72 @@ class Ce {
|
|
|
3432
6066
|
return t.replace(/;\s*$/, "");
|
|
3433
6067
|
}
|
|
3434
6068
|
}
|
|
3435
|
-
async function
|
|
3436
|
-
const t = new
|
|
6069
|
+
async function Hi(s) {
|
|
6070
|
+
const t = new Si(s);
|
|
3437
6071
|
return await t.start(), t;
|
|
3438
6072
|
}
|
|
3439
|
-
async function
|
|
3440
|
-
return
|
|
6073
|
+
async function ts(s) {
|
|
6074
|
+
return Hi(s);
|
|
3441
6075
|
}
|
|
3442
6076
|
export {
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
6077
|
+
P as AIClientError,
|
|
6078
|
+
Gt as BOLD,
|
|
6079
|
+
Vt as DIM,
|
|
6080
|
+
Wt as Database,
|
|
6081
|
+
Si as DuckDBTerminal,
|
|
6082
|
+
tt as FG_BLUE,
|
|
6083
|
+
v as FG_CYAN,
|
|
6084
|
+
b as FG_GREEN,
|
|
6085
|
+
w as FG_RED,
|
|
6086
|
+
et as FG_WHITE,
|
|
6087
|
+
F as FG_YELLOW,
|
|
6088
|
+
ue as HistoryStore,
|
|
6089
|
+
ce as InputBuffer,
|
|
6090
|
+
Re as LinkProvider,
|
|
6091
|
+
At as MAX_URL_LENGTH,
|
|
6092
|
+
Zi as PaginationHandler,
|
|
6093
|
+
V as RESET,
|
|
6094
|
+
gi as SharingModal,
|
|
6095
|
+
jt as TerminalAdapter,
|
|
6096
|
+
L as bold,
|
|
6097
|
+
mi as calculateURLLength,
|
|
6098
|
+
X as checkProxyAvailable,
|
|
6099
|
+
Xi as clearAISettings,
|
|
6100
|
+
Ki as clearShareableURL,
|
|
6101
|
+
d as colorize,
|
|
6102
|
+
qi as containsURL,
|
|
6103
|
+
st as copyToClipboard,
|
|
6104
|
+
Ji as createCommands,
|
|
6105
|
+
Hi as createTerminal,
|
|
6106
|
+
bi as darkTheme,
|
|
6107
|
+
fi as decodeQueryFromURL,
|
|
6108
|
+
m as dim,
|
|
6109
|
+
ts as embed,
|
|
6110
|
+
G as encodeQueryForURL,
|
|
6111
|
+
ji as extractURLs,
|
|
6112
|
+
wt as fetchProviders,
|
|
6113
|
+
Q as formatCSV,
|
|
6114
|
+
z as formatJSON,
|
|
6115
|
+
_ as formatTSV,
|
|
6116
|
+
H as formatTable,
|
|
6117
|
+
Ei as generateSQL,
|
|
6118
|
+
q as generateShareableURL,
|
|
6119
|
+
Rt as getAISettings,
|
|
6120
|
+
mt as getBaseShareURL,
|
|
6121
|
+
gt as getDefaultEndpoint,
|
|
6122
|
+
Ti as getDefaultProvider,
|
|
6123
|
+
Gi as getEncodedQueryLength,
|
|
6124
|
+
xi as getSavedTheme,
|
|
6125
|
+
pt as getTheme,
|
|
6126
|
+
K as highlightSQL,
|
|
6127
|
+
zi as isClipboardAvailable,
|
|
6128
|
+
Te as isSQLComplete,
|
|
6129
|
+
Wi as isValidURL,
|
|
6130
|
+
Ci as lightTheme,
|
|
6131
|
+
$e as linkifyText,
|
|
6132
|
+
Yi as parseShareableURL,
|
|
6133
|
+
xe as readFromClipboard,
|
|
6134
|
+
O as saveAISettings,
|
|
6135
|
+
vi as saveTheme,
|
|
6136
|
+
Vi as wouldExceedLimit
|
|
3483
6137
|
};
|