@calliopelabs/ui 0.14.0 → 0.18.0
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/README.md +235 -1
- package/dist/agent/AgentChat.d.ts +8 -1
- package/dist/agent/AgentChat.js +43 -2
- package/dist/agent/WorkflowRenderers.d.ts +61 -0
- package/dist/agent/WorkflowRenderers.js +102 -0
- package/dist/agent/controller.d.ts +19 -0
- package/dist/agent/controller.js +33 -0
- package/dist/agent/copilotkit/CopilotKitAgentProvider.js +2 -2
- package/dist/agent/index.d.ts +4 -2
- package/dist/agent/index.js +3 -1
- package/dist/agent/mock.d.ts +26 -4
- package/dist/agent/mock.js +68 -37
- package/dist/agent/renderers.d.ts +49 -0
- package/dist/agent/renderers.js +27 -0
- package/dist/appearance/brands.d.ts +17 -0
- package/dist/appearance/brands.js +14 -0
- package/dist/chat/MessageBubble.js +1 -1
- package/dist/chat/MessageList.d.ts +9 -2
- package/dist/chat/MessageList.js +11 -8
- package/dist/css/appearance.js +1 -1
- package/dist/css/calliope.css +5 -0
- package/dist/css/tokens.css +5 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +8 -1
- package/dist/primitives/Badge.d.ts +14 -0
- package/dist/primitives/Badge.js +25 -0
- package/dist/primitives/ContextMenu.d.ts +18 -0
- package/dist/primitives/ContextMenu.js +34 -0
- package/dist/primitives/Menubar.d.ts +21 -0
- package/dist/primitives/Menubar.js +43 -0
- package/dist/primitives/ScrollArea.d.ts +16 -0
- package/dist/primitives/ScrollArea.js +11 -0
- package/dist/primitives/Toast.d.ts +37 -0
- package/dist/primitives/Toast.js +43 -0
- package/dist/primitives/Tooltip.d.ts +16 -0
- package/dist/primitives/Tooltip.js +26 -0
- package/dist/provider/messages.d.ts +217 -0
- package/dist/provider/messages.js +238 -0
- package/dist/shell/RailPanel.js +1 -1
- package/dist/theme/css-layer.js +2 -1
- package/dist/theme/tokens.generated.d.ts +3 -0
- package/dist/theme/tokens.generated.js +3 -0
- package/dist/workbench/ActivityBar.d.ts +37 -0
- package/dist/workbench/ActivityBar.js +57 -0
- package/dist/workbench/BottomPanel.d.ts +30 -0
- package/dist/workbench/BottomPanel.js +20 -0
- package/dist/workbench/CodeEditor.d.ts +48 -0
- package/dist/workbench/CodeEditor.js +153 -0
- package/dist/workbench/CommandPalette.d.ts +49 -0
- package/dist/workbench/CommandPalette.js +114 -0
- package/dist/workbench/DataChart.d.ts +45 -0
- package/dist/workbench/DataChart.js +146 -0
- package/dist/workbench/DataGrid.d.ts +89 -0
- package/dist/workbench/DataGrid.js +590 -0
- package/dist/workbench/DataProfile.d.ts +17 -0
- package/dist/workbench/DataProfile.js +51 -0
- package/dist/workbench/EditorTabs.d.ts +62 -0
- package/dist/workbench/EditorTabs.js +115 -0
- package/dist/workbench/FileBrowser.d.ts +62 -0
- package/dist/workbench/FileBrowser.js +117 -0
- package/dist/workbench/FileGrid.d.ts +13 -0
- package/dist/workbench/FileGrid.js +114 -0
- package/dist/workbench/FileIcon.d.ts +10 -0
- package/dist/workbench/FileIcon.js +24 -0
- package/dist/workbench/FileList.d.ts +35 -0
- package/dist/workbench/FileList.js +160 -0
- package/dist/workbench/FilePreview.d.ts +66 -0
- package/dist/workbench/FilePreview.js +108 -0
- package/dist/workbench/FileTree.d.ts +37 -0
- package/dist/workbench/FileTree.js +218 -0
- package/dist/workbench/GridCell.d.ts +19 -0
- package/dist/workbench/GridCell.js +50 -0
- package/dist/workbench/GridDetails.d.ts +31 -0
- package/dist/workbench/GridDetails.js +78 -0
- package/dist/workbench/MenuBar.d.ts +49 -0
- package/dist/workbench/MenuBar.js +25 -0
- package/dist/workbench/PaneGroup.d.ts +50 -0
- package/dist/workbench/PaneGroup.js +146 -0
- package/dist/workbench/PendingChangesBar.d.ts +20 -0
- package/dist/workbench/PendingChangesBar.js +19 -0
- package/dist/workbench/Shortcuts.d.ts +50 -0
- package/dist/workbench/Shortcuts.js +109 -0
- package/dist/workbench/SqlEditor.d.ts +21 -0
- package/dist/workbench/SqlEditor.js +79 -0
- package/dist/workbench/StatusBar.d.ts +28 -0
- package/dist/workbench/StatusBar.js +26 -0
- package/dist/workbench/TruncationBanner.d.ts +11 -0
- package/dist/workbench/TruncationBanner.js +12 -0
- package/dist/workbench/UploadDropzone.d.ts +52 -0
- package/dist/workbench/UploadDropzone.js +115 -0
- package/dist/workbench/WorkbenchShell.d.ts +36 -0
- package/dist/workbench/WorkbenchShell.js +20 -0
- package/dist/workbench/dnd.d.ts +17 -0
- package/dist/workbench/dnd.js +77 -0
- package/dist/workbench/download.d.ts +11 -0
- package/dist/workbench/download.js +28 -0
- package/dist/workbench/editor-theme.d.ts +42 -0
- package/dist/workbench/editor-theme.js +113 -0
- package/dist/workbench/grid-format.d.ts +59 -0
- package/dist/workbench/grid-format.js +169 -0
- package/dist/workbench/grid-model.d.ts +75 -0
- package/dist/workbench/grid-model.js +259 -0
- package/dist/workbench/grid-selection.d.ts +69 -0
- package/dist/workbench/grid-selection.js +183 -0
- package/dist/workbench/grid-stats.d.ts +58 -0
- package/dist/workbench/grid-stats.js +185 -0
- package/dist/workbench/index.d.ts +44 -0
- package/dist/workbench/index.js +47 -0
- package/dist/workbench/keys.d.ts +23 -0
- package/dist/workbench/keys.js +105 -0
- package/dist/workbench/model.d.ts +72 -0
- package/dist/workbench/model.js +214 -0
- package/dist/workbench/palette.d.ts +17 -0
- package/dist/workbench/palette.js +45 -0
- package/dist/workbench/pending.d.ts +92 -0
- package/dist/workbench/pending.js +207 -0
- package/dist/workbench/selection.d.ts +16 -0
- package/dist/workbench/selection.js +46 -0
- package/dist/workbench/sql-completion.d.ts +107 -0
- package/dist/workbench/sql-completion.js +475 -0
- package/dist/workbench/typeahead.d.ts +15 -0
- package/dist/workbench/typeahead.js +22 -0
- package/dist/workflow/WorkflowRuntimeControls.d.ts +21 -0
- package/dist/workflow/WorkflowRuntimeControls.js +54 -0
- package/dist/workflow/runtime-controls.d.ts +29 -0
- package/dist/workflow/runtime-controls.js +10 -0
- package/package.json +17 -2
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyboard chords as strings: `"Mod+K"`, `"Mod+Shift+A"`, `"Alt+ArrowLeft"`,
|
|
3
|
+
* `"F2"`. `Mod` is Command on a Mac and Control elsewhere, so one binding
|
|
4
|
+
* serves both. Pure: parsing, matching and display, no listeners.
|
|
5
|
+
*/
|
|
6
|
+
const MODIFIERS = new Set(["mod", "ctrl", "control", "meta", "cmd", "alt", "option", "shift"]);
|
|
7
|
+
export function parseShortcut(shortcut) {
|
|
8
|
+
const chord = {
|
|
9
|
+
key: "",
|
|
10
|
+
mod: false,
|
|
11
|
+
ctrl: false,
|
|
12
|
+
meta: false,
|
|
13
|
+
alt: false,
|
|
14
|
+
shift: false,
|
|
15
|
+
};
|
|
16
|
+
// "Mod++" binds the plus key: the last part is the key, even when empty.
|
|
17
|
+
const plus = shortcut === "+" || shortcut.endsWith("++");
|
|
18
|
+
const parts = (plus ? shortcut.slice(0, -1) : shortcut).split("+");
|
|
19
|
+
const last = parts.pop() ?? "";
|
|
20
|
+
const raw = plus ? "+" : last;
|
|
21
|
+
for (const part of parts) {
|
|
22
|
+
const p = part.trim().toLowerCase();
|
|
23
|
+
if (!MODIFIERS.has(p))
|
|
24
|
+
throw new Error(`Unknown modifier "${part}" in "${shortcut}"`);
|
|
25
|
+
if (p === "mod")
|
|
26
|
+
chord.mod = true;
|
|
27
|
+
else if (p === "ctrl" || p === "control")
|
|
28
|
+
chord.ctrl = true;
|
|
29
|
+
else if (p === "meta" || p === "cmd")
|
|
30
|
+
chord.meta = true;
|
|
31
|
+
else if (p === "alt" || p === "option")
|
|
32
|
+
chord.alt = true;
|
|
33
|
+
else
|
|
34
|
+
chord.shift = true;
|
|
35
|
+
}
|
|
36
|
+
const key = raw.trim() || raw;
|
|
37
|
+
chord.key = key.length === 1 ? key.toLowerCase() : key;
|
|
38
|
+
return chord;
|
|
39
|
+
}
|
|
40
|
+
export function isMacPlatform() {
|
|
41
|
+
if (typeof navigator === "undefined")
|
|
42
|
+
return false;
|
|
43
|
+
const nav = navigator;
|
|
44
|
+
return /mac|iphone|ipad/i.test(nav.userAgentData?.platform ?? nav.platform ?? "");
|
|
45
|
+
}
|
|
46
|
+
/** The key a physical code stands for, so Alt and Shift layouts still match letters and digits. */
|
|
47
|
+
function keyFromCode(code) {
|
|
48
|
+
const m = /^(?:Key([A-Z])|Digit(\d))$/.exec(code);
|
|
49
|
+
if (!m)
|
|
50
|
+
return null;
|
|
51
|
+
return (m[1] ?? m[2] ?? "").toLowerCase();
|
|
52
|
+
}
|
|
53
|
+
export function matchesShortcut(event, shortcut, isMac = isMacPlatform()) {
|
|
54
|
+
const chord = typeof shortcut === "string" ? parseShortcut(shortcut) : shortcut;
|
|
55
|
+
const wantCtrl = chord.ctrl || (chord.mod && !isMac);
|
|
56
|
+
const wantMeta = chord.meta || (chord.mod && isMac);
|
|
57
|
+
if (event.ctrlKey !== wantCtrl || event.metaKey !== wantMeta || event.altKey !== chord.alt) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const key = event.key.length === 1 ? event.key.toLowerCase() : event.key;
|
|
61
|
+
// A shifted symbol ("?" for Shift+/) carries its shift in the key itself.
|
|
62
|
+
if (event.shiftKey !== chord.shift &&
|
|
63
|
+
!(key === chord.key && key.length === 1 && !/[a-z0-9]/.test(key))) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return key === chord.key || keyFromCode(event.code) === chord.key;
|
|
67
|
+
}
|
|
68
|
+
const MAC_GLYPH = {
|
|
69
|
+
mod: "⌘",
|
|
70
|
+
meta: "⌘",
|
|
71
|
+
ctrl: "⌃",
|
|
72
|
+
alt: "⌥",
|
|
73
|
+
shift: "⇧",
|
|
74
|
+
};
|
|
75
|
+
const KEY_NAME = {
|
|
76
|
+
ArrowUp: "↑",
|
|
77
|
+
ArrowDown: "↓",
|
|
78
|
+
ArrowLeft: "←",
|
|
79
|
+
ArrowRight: "→",
|
|
80
|
+
Enter: "Enter",
|
|
81
|
+
Escape: "Esc",
|
|
82
|
+
Backspace: "Backspace",
|
|
83
|
+
Delete: "Del",
|
|
84
|
+
" ": "Space",
|
|
85
|
+
};
|
|
86
|
+
/** The keys of a chord as they are drawn: `["⌘", "K"]` on a Mac, `["Ctrl", "K"]` elsewhere. */
|
|
87
|
+
export function shortcutKeys(shortcut, isMac = isMacPlatform()) {
|
|
88
|
+
const chord = parseShortcut(shortcut);
|
|
89
|
+
const keys = [];
|
|
90
|
+
const add = (name, label) => keys.push(isMac ? (MAC_GLYPH[name] ?? label) : label);
|
|
91
|
+
if (chord.ctrl || (chord.mod && !isMac))
|
|
92
|
+
add("ctrl", "Ctrl");
|
|
93
|
+
if (chord.alt)
|
|
94
|
+
add("alt", "Alt");
|
|
95
|
+
if (chord.shift)
|
|
96
|
+
add("shift", "Shift");
|
|
97
|
+
if (chord.meta || (chord.mod && isMac))
|
|
98
|
+
add("meta", "Meta");
|
|
99
|
+
keys.push(KEY_NAME[chord.key] ?? (chord.key.length === 1 ? chord.key.toUpperCase() : chord.key));
|
|
100
|
+
return keys;
|
|
101
|
+
}
|
|
102
|
+
/** One string for a menu or a tooltip: `"⌘K"` on a Mac, `"Ctrl+K"` elsewhere. */
|
|
103
|
+
export function formatShortcut(shortcut, isMac = isMacPlatform()) {
|
|
104
|
+
return shortcutKeys(shortcut, isMac).join(isMac ? "" : "+");
|
|
105
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The file browser's data: plain values the host maps its own listing onto.
|
|
3
|
+
* Pure (no "use client"), so a server can sort or build fixtures with it.
|
|
4
|
+
*/
|
|
5
|
+
export type FileKind = "file" | "directory";
|
|
6
|
+
/** One entry of a listing. `path` is its identity: unique, `/`-separated, no leading slash. */
|
|
7
|
+
export interface FileItem {
|
|
8
|
+
path: string;
|
|
9
|
+
name: string;
|
|
10
|
+
kind: FileKind;
|
|
11
|
+
/** Bytes. Absent or null for directories and unknown sizes. */
|
|
12
|
+
size?: number | null;
|
|
13
|
+
/** ISO 8601 or a Date. */
|
|
14
|
+
modified?: string | Date | null;
|
|
15
|
+
mimeType?: string | null;
|
|
16
|
+
}
|
|
17
|
+
export type FileSortKey = "name" | "size" | "modified" | "kind";
|
|
18
|
+
export interface FileSort {
|
|
19
|
+
key: FileSortKey;
|
|
20
|
+
dir: "asc" | "desc";
|
|
21
|
+
}
|
|
22
|
+
export type FileView = "list" | "grid";
|
|
23
|
+
/** Where an upload stands. */
|
|
24
|
+
export type UploadState = "queued" | "uploading" | "done" | "error" | "canceled";
|
|
25
|
+
/**
|
|
26
|
+
* A directory's children as the host knows them. Absent from the map means
|
|
27
|
+
* "never asked for": the tree calls `onLoad` when it first opens.
|
|
28
|
+
*/
|
|
29
|
+
export type FileBranch = {
|
|
30
|
+
state: "loading";
|
|
31
|
+
} | {
|
|
32
|
+
state: "error";
|
|
33
|
+
message?: string;
|
|
34
|
+
} | {
|
|
35
|
+
state: "ready";
|
|
36
|
+
items: readonly FileItem[];
|
|
37
|
+
};
|
|
38
|
+
/** The parent directory of a path; the root is "". */
|
|
39
|
+
export declare function parentPath(path: string): string;
|
|
40
|
+
/** A path under a directory; the root is "". */
|
|
41
|
+
export declare function joinPath(dir: string, name: string): string;
|
|
42
|
+
/** True when `path` is `ancestor` or lies under it. */
|
|
43
|
+
export declare function isWithin(path: string, ancestor: string): boolean;
|
|
44
|
+
/** The lower-case extension without the dot, or "" (".bashrc" has none). */
|
|
45
|
+
export declare function fileExtension(name: string): string;
|
|
46
|
+
/** The breadcrumb trail of a path: the root, then each directory down to it. */
|
|
47
|
+
export declare function pathSegments(path: string): {
|
|
48
|
+
name: string;
|
|
49
|
+
path: string;
|
|
50
|
+
}[];
|
|
51
|
+
/**
|
|
52
|
+
* Whether moving `sources` into `target` does anything sensible: never into
|
|
53
|
+
* itself or its own subtree, and a move into the folder it is already in is
|
|
54
|
+
* a no-op. Copies (`copy`) may land in the same folder.
|
|
55
|
+
*/
|
|
56
|
+
export declare function canDrop(sources: readonly string[], target: string, copy?: boolean): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* A listing in display order: directories first, then by the key, ties and
|
|
59
|
+
* missing values by name. Natural order, so "file10" follows "file9".
|
|
60
|
+
*/
|
|
61
|
+
export declare function sortFiles(items: readonly FileItem[], sort: FileSort): FileItem[];
|
|
62
|
+
/** Bytes for people: 0 B, 912 B, 1.5 KB, 23 MB (1024-based, as file managers show). */
|
|
63
|
+
export declare function formatBytes(bytes: number, locale?: string): string;
|
|
64
|
+
/** What a preview can show for a file, from its name and MIME type. */
|
|
65
|
+
export type PreviewKind = "csv" | "json" | "image" | "markdown" | "text" | "binary";
|
|
66
|
+
export declare function previewKindFor(name: string, mimeType?: string | null): PreviewKind;
|
|
67
|
+
/**
|
|
68
|
+
* Parses CSV (RFC 4180: quoted fields, doubled quotes, newlines inside
|
|
69
|
+
* quotes) or, with `delimiter: "\t"`, TSV. Returns rows of strings; the
|
|
70
|
+
* caller decides whether the first row is a header.
|
|
71
|
+
*/
|
|
72
|
+
export declare function parseDelimited(text: string, delimiter?: string): string[][];
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The file browser's data: plain values the host maps its own listing onto.
|
|
3
|
+
* Pure (no "use client"), so a server can sort or build fixtures with it.
|
|
4
|
+
*/
|
|
5
|
+
/** The parent directory of a path; the root is "". */
|
|
6
|
+
export function parentPath(path) {
|
|
7
|
+
const i = path.lastIndexOf("/");
|
|
8
|
+
return i < 0 ? "" : path.slice(0, i);
|
|
9
|
+
}
|
|
10
|
+
/** A path under a directory; the root is "". */
|
|
11
|
+
export function joinPath(dir, name) {
|
|
12
|
+
return dir ? `${dir}/${name}` : name;
|
|
13
|
+
}
|
|
14
|
+
/** True when `path` is `ancestor` or lies under it. */
|
|
15
|
+
export function isWithin(path, ancestor) {
|
|
16
|
+
return ancestor === "" || path === ancestor || path.startsWith(`${ancestor}/`);
|
|
17
|
+
}
|
|
18
|
+
/** The lower-case extension without the dot, or "" (".bashrc" has none). */
|
|
19
|
+
export function fileExtension(name) {
|
|
20
|
+
const i = name.lastIndexOf(".");
|
|
21
|
+
return i > 0 ? name.slice(i + 1).toLowerCase() : "";
|
|
22
|
+
}
|
|
23
|
+
/** The breadcrumb trail of a path: the root, then each directory down to it. */
|
|
24
|
+
export function pathSegments(path) {
|
|
25
|
+
const parts = path.split("/").filter(Boolean);
|
|
26
|
+
return parts.map((name, i) => ({ name, path: parts.slice(0, i + 1).join("/") }));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Whether moving `sources` into `target` does anything sensible: never into
|
|
30
|
+
* itself or its own subtree, and a move into the folder it is already in is
|
|
31
|
+
* a no-op. Copies (`copy`) may land in the same folder.
|
|
32
|
+
*/
|
|
33
|
+
export function canDrop(sources, target, copy = false) {
|
|
34
|
+
return (sources.length > 0 &&
|
|
35
|
+
sources.every((src) => !(target === src || target.startsWith(`${src}/`)) && (copy || parentPath(src) !== target)));
|
|
36
|
+
}
|
|
37
|
+
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" });
|
|
38
|
+
function time(value) {
|
|
39
|
+
if (value == null)
|
|
40
|
+
return null;
|
|
41
|
+
const t = value instanceof Date ? value.getTime() : Date.parse(value);
|
|
42
|
+
return Number.isNaN(t) ? null : t;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A listing in display order: directories first, then by the key, ties and
|
|
46
|
+
* missing values by name. Natural order, so "file10" follows "file9".
|
|
47
|
+
*/
|
|
48
|
+
export function sortFiles(items, sort) {
|
|
49
|
+
const sign = sort.dir === "asc" ? 1 : -1;
|
|
50
|
+
const byName = (a, b) => collator.compare(a.name, b.name);
|
|
51
|
+
const compare = (a, b) => {
|
|
52
|
+
switch (sort.key) {
|
|
53
|
+
case "size": {
|
|
54
|
+
const x = a.size ?? null;
|
|
55
|
+
const y = b.size ?? null;
|
|
56
|
+
if (x === y)
|
|
57
|
+
return 0;
|
|
58
|
+
if (x === null)
|
|
59
|
+
return 1;
|
|
60
|
+
if (y === null)
|
|
61
|
+
return -1;
|
|
62
|
+
return (x - y) * sign;
|
|
63
|
+
}
|
|
64
|
+
case "modified": {
|
|
65
|
+
const x = time(a.modified);
|
|
66
|
+
const y = time(b.modified);
|
|
67
|
+
if (x === y)
|
|
68
|
+
return 0;
|
|
69
|
+
if (x === null)
|
|
70
|
+
return 1;
|
|
71
|
+
if (y === null)
|
|
72
|
+
return -1;
|
|
73
|
+
return (x - y) * sign;
|
|
74
|
+
}
|
|
75
|
+
case "kind":
|
|
76
|
+
return collator.compare(fileExtension(a.name), fileExtension(b.name)) * sign;
|
|
77
|
+
case "name":
|
|
78
|
+
return byName(a, b) * sign;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
return [...items].sort((a, b) => {
|
|
82
|
+
if (a.kind !== b.kind)
|
|
83
|
+
return a.kind === "directory" ? -1 : 1;
|
|
84
|
+
return compare(a, b) || byName(a, b);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/** Bytes for people: 0 B, 912 B, 1.5 KB, 23 MB (1024-based, as file managers show). */
|
|
88
|
+
export function formatBytes(bytes, locale) {
|
|
89
|
+
if (!Number.isFinite(bytes) || bytes < 0)
|
|
90
|
+
return "";
|
|
91
|
+
const units = ["B", "KB", "MB", "GB", "TB", "PB"];
|
|
92
|
+
let value = bytes;
|
|
93
|
+
let unit = 0;
|
|
94
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
95
|
+
value /= 1024;
|
|
96
|
+
unit++;
|
|
97
|
+
}
|
|
98
|
+
const digits = unit === 0 || value >= 10 ? 0 : 1;
|
|
99
|
+
return `${value.toLocaleString(locale, { maximumFractionDigits: digits })} ${units[unit]}`;
|
|
100
|
+
}
|
|
101
|
+
const TEXT_EXTENSIONS = new Set([
|
|
102
|
+
"txt",
|
|
103
|
+
"log",
|
|
104
|
+
"py",
|
|
105
|
+
"js",
|
|
106
|
+
"jsx",
|
|
107
|
+
"ts",
|
|
108
|
+
"tsx",
|
|
109
|
+
"mjs",
|
|
110
|
+
"cjs",
|
|
111
|
+
"rs",
|
|
112
|
+
"go",
|
|
113
|
+
"java",
|
|
114
|
+
"kt",
|
|
115
|
+
"rb",
|
|
116
|
+
"sh",
|
|
117
|
+
"bash",
|
|
118
|
+
"zsh",
|
|
119
|
+
"sql",
|
|
120
|
+
"html",
|
|
121
|
+
"css",
|
|
122
|
+
"scss",
|
|
123
|
+
"xml",
|
|
124
|
+
"yaml",
|
|
125
|
+
"yml",
|
|
126
|
+
"toml",
|
|
127
|
+
"ini",
|
|
128
|
+
"cfg",
|
|
129
|
+
"conf",
|
|
130
|
+
"env",
|
|
131
|
+
"r",
|
|
132
|
+
"c",
|
|
133
|
+
"h",
|
|
134
|
+
"cpp",
|
|
135
|
+
"hpp",
|
|
136
|
+
"cs",
|
|
137
|
+
"php",
|
|
138
|
+
"swift",
|
|
139
|
+
"lua",
|
|
140
|
+
"dockerfile",
|
|
141
|
+
"tf",
|
|
142
|
+
]);
|
|
143
|
+
const IMAGE_EXTENSIONS = new Set([
|
|
144
|
+
"png",
|
|
145
|
+
"jpg",
|
|
146
|
+
"jpeg",
|
|
147
|
+
"gif",
|
|
148
|
+
"svg",
|
|
149
|
+
"webp",
|
|
150
|
+
"avif",
|
|
151
|
+
"bmp",
|
|
152
|
+
"ico",
|
|
153
|
+
]);
|
|
154
|
+
export function previewKindFor(name, mimeType) {
|
|
155
|
+
const ext = fileExtension(name);
|
|
156
|
+
if (ext === "csv" || ext === "tsv" || mimeType === "text/csv")
|
|
157
|
+
return "csv";
|
|
158
|
+
if (ext === "json" || ext === "jsonl" || ext === "ipynb" || mimeType === "application/json")
|
|
159
|
+
return "json";
|
|
160
|
+
if (IMAGE_EXTENSIONS.has(ext) || mimeType?.startsWith("image/"))
|
|
161
|
+
return "image";
|
|
162
|
+
if (ext === "md" || ext === "markdown" || mimeType === "text/markdown")
|
|
163
|
+
return "markdown";
|
|
164
|
+
if (TEXT_EXTENSIONS.has(ext) || mimeType?.startsWith("text/"))
|
|
165
|
+
return "text";
|
|
166
|
+
return "binary";
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Parses CSV (RFC 4180: quoted fields, doubled quotes, newlines inside
|
|
170
|
+
* quotes) or, with `delimiter: "\t"`, TSV. Returns rows of strings; the
|
|
171
|
+
* caller decides whether the first row is a header.
|
|
172
|
+
*/
|
|
173
|
+
export function parseDelimited(text, delimiter = ",") {
|
|
174
|
+
const rows = [];
|
|
175
|
+
let row = [];
|
|
176
|
+
let field = "";
|
|
177
|
+
let quoted = false;
|
|
178
|
+
for (let i = 0; i < text.length; i++) {
|
|
179
|
+
const c = text[i];
|
|
180
|
+
if (quoted) {
|
|
181
|
+
if (c === '"') {
|
|
182
|
+
if (text[i + 1] === '"') {
|
|
183
|
+
field += '"';
|
|
184
|
+
i++;
|
|
185
|
+
}
|
|
186
|
+
else
|
|
187
|
+
quoted = false;
|
|
188
|
+
}
|
|
189
|
+
else
|
|
190
|
+
field += c;
|
|
191
|
+
}
|
|
192
|
+
else if (c === '"' && field === "")
|
|
193
|
+
quoted = true;
|
|
194
|
+
else if (c === delimiter) {
|
|
195
|
+
row.push(field);
|
|
196
|
+
field = "";
|
|
197
|
+
}
|
|
198
|
+
else if (c === "\n" || c === "\r") {
|
|
199
|
+
if (c === "\r" && text[i + 1] === "\n")
|
|
200
|
+
i++;
|
|
201
|
+
row.push(field);
|
|
202
|
+
rows.push(row);
|
|
203
|
+
row = [];
|
|
204
|
+
field = "";
|
|
205
|
+
}
|
|
206
|
+
else
|
|
207
|
+
field += c;
|
|
208
|
+
}
|
|
209
|
+
if (field !== "" || row.length > 0) {
|
|
210
|
+
row.push(field);
|
|
211
|
+
rows.push(row);
|
|
212
|
+
}
|
|
213
|
+
return rows;
|
|
214
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The command palette's matching: pure, so a host can rank its own lists
|
|
3
|
+
* with the same rules the palette uses.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* How well `query` matches `text` as a subsequence, higher is better, or -1
|
|
7
|
+
* when it does not match. Consecutive characters, word starts and an exact
|
|
8
|
+
* prefix score more, so "nq" ranks "New Query" above "Run query".
|
|
9
|
+
*/
|
|
10
|
+
export declare function fuzzyScore(text: string, query: string): number;
|
|
11
|
+
export interface PaletteMatchable {
|
|
12
|
+
label: string;
|
|
13
|
+
keywords?: string;
|
|
14
|
+
group?: string;
|
|
15
|
+
}
|
|
16
|
+
/** The items that match, best first; ties keep the given order. */
|
|
17
|
+
export declare function rankItems<T extends PaletteMatchable>(items: readonly T[], query: string): T[];
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The command palette's matching: pure, so a host can rank its own lists
|
|
3
|
+
* with the same rules the palette uses.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* How well `query` matches `text` as a subsequence, higher is better, or -1
|
|
7
|
+
* when it does not match. Consecutive characters, word starts and an exact
|
|
8
|
+
* prefix score more, so "nq" ranks "New Query" above "Run query".
|
|
9
|
+
*/
|
|
10
|
+
export function fuzzyScore(text, query) {
|
|
11
|
+
const q = query.trim().toLowerCase();
|
|
12
|
+
if (q === "")
|
|
13
|
+
return 0;
|
|
14
|
+
const t = text.toLowerCase();
|
|
15
|
+
if (t.startsWith(q))
|
|
16
|
+
return 1000 - t.length;
|
|
17
|
+
let score = 0;
|
|
18
|
+
let ti = 0;
|
|
19
|
+
let streak = 0;
|
|
20
|
+
for (const ch of q) {
|
|
21
|
+
if (ch === " ")
|
|
22
|
+
continue;
|
|
23
|
+
const found = t.indexOf(ch, ti);
|
|
24
|
+
if (found === -1)
|
|
25
|
+
return -1;
|
|
26
|
+
const wordStart = found === 0 || /[\s\-_./:]/.test(t[found - 1] ?? "");
|
|
27
|
+
streak = found === ti ? streak + 1 : 0;
|
|
28
|
+
score += 1 + streak * 3 + (wordStart ? 8 : 0) - Math.min(found - ti, 10) * 0.1;
|
|
29
|
+
ti = found + 1;
|
|
30
|
+
}
|
|
31
|
+
return score;
|
|
32
|
+
}
|
|
33
|
+
/** The items that match, best first; ties keep the given order. */
|
|
34
|
+
export function rankItems(items, query) {
|
|
35
|
+
if (query.trim() === "")
|
|
36
|
+
return [...items];
|
|
37
|
+
return items
|
|
38
|
+
.map((item, index) => {
|
|
39
|
+
const score = Math.max(fuzzyScore(item.label, query), item.keywords ? fuzzyScore(item.keywords, query) - 1 : -1);
|
|
40
|
+
return { item, index, score };
|
|
41
|
+
})
|
|
42
|
+
.filter((r) => r.score >= 0)
|
|
43
|
+
.sort((a, b) => b.score - a.score || a.index - b.index)
|
|
44
|
+
.map((r) => r.item);
|
|
45
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type ColumnType, type GridRow } from "./grid-model.js";
|
|
2
|
+
export type PendingState = "modified" | "added" | "deleted";
|
|
3
|
+
/** One row that differs from what the host holds. */
|
|
4
|
+
export interface PendingRow {
|
|
5
|
+
state: PendingState;
|
|
6
|
+
/** The row as it was read; empty for an inserted row. */
|
|
7
|
+
original: GridRow;
|
|
8
|
+
/** The row as it will be written. */
|
|
9
|
+
values: GridRow;
|
|
10
|
+
}
|
|
11
|
+
export interface PendingChanges {
|
|
12
|
+
readonly rows: ReadonlyMap<number, PendingRow>;
|
|
13
|
+
/** Rows inserted so far, for the next insert's key. */
|
|
14
|
+
readonly inserted: number;
|
|
15
|
+
}
|
|
16
|
+
export declare const NO_CHANGES: PendingChanges;
|
|
17
|
+
/**
|
|
18
|
+
* Sets one cell. `source` is the row as read (unused for an inserted row).
|
|
19
|
+
* An edit back to the original value drops the row's entry.
|
|
20
|
+
*/
|
|
21
|
+
export declare function editCell(changes: PendingChanges, key: number, column: string, value: unknown, source: GridRow | undefined): PendingChanges;
|
|
22
|
+
/** Inserts a row of NULLs, or a copy of `from` (duplicate). Returns the new row's key. */
|
|
23
|
+
export declare function addRow(changes: PendingChanges, columns: readonly string[], from?: GridRow): {
|
|
24
|
+
changes: PendingChanges;
|
|
25
|
+
key: number;
|
|
26
|
+
};
|
|
27
|
+
/** Marks rows deleted; an inserted row is simply dropped. */
|
|
28
|
+
export declare function deleteRows(changes: PendingChanges, keys: Iterable<number>, sourceOf: (key: number) => GridRow | undefined): PendingChanges;
|
|
29
|
+
/** Forgets every change to one row (an inserted row disappears). */
|
|
30
|
+
export declare function revertRow(changes: PendingChanges, key: number): PendingChanges;
|
|
31
|
+
/** The value a cell shows: the pending value when there is one. */
|
|
32
|
+
export declare function pendingValue(changes: PendingChanges, key: number, column: string, source: GridRow | undefined): unknown;
|
|
33
|
+
/** True when the cell will be written: every cell of an inserted row, the edited cells of a modified one. */
|
|
34
|
+
export declare function isCellChanged(changes: PendingChanges, key: number, column: string): boolean;
|
|
35
|
+
/** The keys of the inserted rows, in the order they were added. */
|
|
36
|
+
export declare function insertedKeys(changes: PendingChanges): number[];
|
|
37
|
+
export interface PendingCounts {
|
|
38
|
+
inserts: number;
|
|
39
|
+
updates: number;
|
|
40
|
+
deletes: number;
|
|
41
|
+
total: number;
|
|
42
|
+
}
|
|
43
|
+
export declare function pendingCounts(changes: PendingChanges): PendingCounts;
|
|
44
|
+
/** One statement's worth of a commit. `key` is the grid's row key, for reporting a failure back. */
|
|
45
|
+
export interface ChangeSetInsert {
|
|
46
|
+
key: number;
|
|
47
|
+
/** The cells the person filled; an untouched NULL is left to the column's default. */
|
|
48
|
+
values: Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
export interface ChangeSetUpdate {
|
|
51
|
+
key: number;
|
|
52
|
+
/** The primary key values the row was read under. */
|
|
53
|
+
primaryKey: Record<string, unknown>;
|
|
54
|
+
/** The changed cells only. */
|
|
55
|
+
values: Record<string, unknown>;
|
|
56
|
+
/** The old values of exactly those cells, for an optimistic-concurrency check (JSON and binary cells left out). */
|
|
57
|
+
expected: Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
export interface ChangeSetDelete {
|
|
60
|
+
key: number;
|
|
61
|
+
primaryKey: Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
/** What a commit hands the host. */
|
|
64
|
+
export interface ChangeSet {
|
|
65
|
+
inserts: ChangeSetInsert[];
|
|
66
|
+
updates: ChangeSetUpdate[];
|
|
67
|
+
deletes: ChangeSetDelete[];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The pending changes as a batch to write. Updates and deletes are keyed on
|
|
71
|
+
* `primaryKeys`; without one they cannot name a row, and the grid only
|
|
72
|
+
* offers inserts.
|
|
73
|
+
*/
|
|
74
|
+
export declare function toChangeSet(changes: PendingChanges, options: {
|
|
75
|
+
columns: readonly string[];
|
|
76
|
+
primaryKeys: readonly string[];
|
|
77
|
+
}): ChangeSet;
|
|
78
|
+
/**
|
|
79
|
+
* A change set as SQL statements, for a host that runs SQL rather than an
|
|
80
|
+
* apply endpoint: INSERT, UPDATE ... WHERE <primary key>, DELETE ... WHERE
|
|
81
|
+
* <primary key>. Literals are quoted by `sqlLiteral`; dialect differences
|
|
82
|
+
* beyond ANSI quoting are the host's.
|
|
83
|
+
*/
|
|
84
|
+
export declare function changeSetToSql(changeSet: ChangeSet, options: {
|
|
85
|
+
table: string;
|
|
86
|
+
}): string[];
|
|
87
|
+
/**
|
|
88
|
+
* What a typed cell means: empty or "NULL" is NULL; a number or boolean
|
|
89
|
+
* column (by its type, or the original value's) keeps its type when the
|
|
90
|
+
* text parses as one.
|
|
91
|
+
*/
|
|
92
|
+
export declare function parseCellInput(text: string, original: unknown, type?: ColumnType): unknown;
|