@flanksource/clicky-ui 0.3.58 → 0.3.59
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/clicky.cjs +3 -1
- package/dist/clicky.d.ts +1 -0
- package/dist/clicky.d.ts.map +1 -1
- package/dist/clicky.js +3 -1
- package/dist/data/Clicky.cjs +22 -270
- package/dist/data/Clicky.cjs.map +1 -1
- package/dist/data/Clicky.d.ts +3 -29
- package/dist/data/Clicky.d.ts.map +1 -1
- package/dist/data/Clicky.js +10 -259
- package/dist/data/Clicky.js.map +1 -1
- package/dist/data/clicky-download.cjs +251 -0
- package/dist/data/clicky-download.cjs.map +1 -0
- package/dist/data/clicky-download.d.ts +55 -0
- package/dist/data/clicky-download.d.ts.map +1 -0
- package/dist/data/clicky-download.js +244 -0
- package/dist/data/clicky-download.js.map +1 -0
- package/dist/data/traces/index.d.ts +2 -0
- package/dist/data/traces/index.d.ts.map +1 -1
- package/dist/data/traces/otel/OtelTraceDetail.cjs +102 -78
- package/dist/data/traces/otel/OtelTraceDetail.cjs.map +1 -1
- package/dist/data/traces/otel/OtelTraceDetail.d.ts +6 -2
- package/dist/data/traces/otel/OtelTraceDetail.d.ts.map +1 -1
- package/dist/data/traces/otel/OtelTraceDetail.js +102 -78
- package/dist/data/traces/otel/OtelTraceDetail.js.map +1 -1
- package/dist/data/traces/otel/OtelTraceTable.cjs +43 -7
- package/dist/data/traces/otel/OtelTraceTable.cjs.map +1 -1
- package/dist/data/traces/otel/OtelTraceTable.d.ts +14 -3
- package/dist/data/traces/otel/OtelTraceTable.d.ts.map +1 -1
- package/dist/data/traces/otel/OtelTraceTable.js +44 -8
- package/dist/data/traces/otel/OtelTraceTable.js.map +1 -1
- package/dist/data/traces/otel/profile-listing.cjs +61 -0
- package/dist/data/traces/otel/profile-listing.cjs.map +1 -0
- package/dist/data/traces/otel/profile-listing.d.ts +23 -0
- package/dist/data/traces/otel/profile-listing.d.ts.map +1 -0
- package/dist/data/traces/otel/profile-listing.js +60 -0
- package/dist/data/traces/otel/profile-listing.js.map +1 -0
- package/dist/data/traces/otel/profile-spans.cjs +22 -0
- package/dist/data/traces/otel/profile-spans.cjs.map +1 -0
- package/dist/data/traces/otel/profile-spans.d.ts +4 -0
- package/dist/data/traces/otel/profile-spans.d.ts.map +1 -0
- package/dist/data/traces/otel/profile-spans.js +22 -0
- package/dist/data/traces/otel/profile-spans.js.map +1 -0
- package/dist/data/traces/otel/span-traces.cjs +7 -1
- package/dist/data/traces/otel/span-traces.cjs.map +1 -1
- package/dist/data/traces/otel/span-traces.d.ts +2 -0
- package/dist/data/traces/otel/span-traces.d.ts.map +1 -1
- package/dist/data/traces/otel/span-traces.js +7 -2
- package/dist/data/traces/otel/span-traces.js.map +1 -1
- package/dist/data/useClickyTableMenu.cjs +33 -0
- package/dist/data/useClickyTableMenu.cjs.map +1 -0
- package/dist/data/useClickyTableMenu.d.ts +17 -0
- package/dist/data/useClickyTableMenu.d.ts.map +1 -0
- package/dist/data/useClickyTableMenu.js +33 -0
- package/dist/data/useClickyTableMenu.js.map +1 -0
- package/dist/data/version-info.cjs +3 -3
- package/dist/data/version-info.js +3 -3
- package/dist/data.cjs +7 -2
- package/dist/data.js +5 -3
- package/dist/index.cjs +14 -3
- package/dist/index.js +9 -4
- package/dist/rpc/EntityExplorerApp.cjs +49 -12
- package/dist/rpc/EntityExplorerApp.cjs.map +1 -1
- package/dist/rpc/EntityExplorerApp.d.ts +10 -1
- package/dist/rpc/EntityExplorerApp.d.ts.map +1 -1
- package/dist/rpc/EntityExplorerApp.js +51 -14
- package/dist/rpc/EntityExplorerApp.js.map +1 -1
- package/dist/rpc/EntitySurface.cjs +38 -0
- package/dist/rpc/EntitySurface.cjs.map +1 -0
- package/dist/rpc/EntitySurface.d.ts +17 -0
- package/dist/rpc/EntitySurface.d.ts.map +1 -0
- package/dist/rpc/EntitySurface.js +38 -0
- package/dist/rpc/EntitySurface.js.map +1 -0
- package/dist/rpc/ExplorerNav.cjs +48 -0
- package/dist/rpc/ExplorerNav.cjs.map +1 -0
- package/dist/rpc/ExplorerNav.d.ts +12 -0
- package/dist/rpc/ExplorerNav.d.ts.map +1 -0
- package/dist/rpc/ExplorerNav.js +48 -0
- package/dist/rpc/ExplorerNav.js.map +1 -0
- package/dist/rpc/explorer-nav-context.cjs +5 -0
- package/dist/rpc/explorer-nav-context.cjs.map +1 -0
- package/dist/rpc/explorer-nav-context.d.ts +11 -0
- package/dist/rpc/explorer-nav-context.d.ts.map +1 -0
- package/dist/rpc/explorer-nav-context.js +6 -0
- package/dist/rpc/explorer-nav-context.js.map +1 -0
- package/dist/rpc.cjs +4 -0
- package/dist/rpc.d.ts +2 -0
- package/dist/rpc.d.ts.map +1 -1
- package/dist/rpc.js +3 -1
- package/package.json +1 -1
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
const require_UiEye = require("../icons/components/UiEye.cjs");
|
|
2
|
+
const require_UiTable = require("../icons/components/UiTable.cjs");
|
|
3
|
+
const require_UiComment = require("../icons/components/UiComment.cjs");
|
|
4
|
+
const require_UiFileCode = require("../icons/components/UiFileCode.cjs");
|
|
5
|
+
const require_UiJson = require("../icons/components/UiJson.cjs");
|
|
6
|
+
const require_UiMarkdown = require("../icons/components/UiMarkdown.cjs");
|
|
7
|
+
const require_UiFileText = require("../icons/components/UiFileText.cjs");
|
|
8
|
+
const require_UiFileSpreadsheet = require("../icons/components/UiFileSpreadsheet.cjs");
|
|
9
|
+
const require_string = require("../lib/string.cjs");
|
|
10
|
+
// src/data/clicky-download.ts
|
|
11
|
+
var CLICKY_PRIMARY_VIEW_FORMATS = ["clicky", "json"];
|
|
12
|
+
var CLICKY_OVERFLOW_VIEW_FORMATS = [
|
|
13
|
+
"pdf",
|
|
14
|
+
"html",
|
|
15
|
+
"markdown",
|
|
16
|
+
"yaml",
|
|
17
|
+
"csv",
|
|
18
|
+
"pretty",
|
|
19
|
+
"excel",
|
|
20
|
+
"slack"
|
|
21
|
+
];
|
|
22
|
+
var CLICKY_DOWNLOAD_FORMATS = [
|
|
23
|
+
"yaml",
|
|
24
|
+
"json",
|
|
25
|
+
"csv",
|
|
26
|
+
"pdf",
|
|
27
|
+
"markdown"
|
|
28
|
+
];
|
|
29
|
+
function getDownloadFormats({ url, download }) {
|
|
30
|
+
if (!(download ? download.all ?? true : !!url) || !url) return [];
|
|
31
|
+
return download?.formats?.length ? [...download.formats] : [...CLICKY_DOWNLOAD_FORMATS];
|
|
32
|
+
}
|
|
33
|
+
function getDownloadScopes(download) {
|
|
34
|
+
return download?.scopes?.length ? download.scopes : [void 0];
|
|
35
|
+
}
|
|
36
|
+
function downloadDescription(format, scope, download) {
|
|
37
|
+
const maxRows = download?.formatMaxRows?.[format];
|
|
38
|
+
if (maxRows) return `Limited to ${maxRows.toLocaleString()} rows`;
|
|
39
|
+
if (scope === "all" && download?.allRowsMode === "buffered") return "Compatibility mode buffers the complete result";
|
|
40
|
+
if (scope === "all" && download?.allRowsMode === "streaming") return "Streams rows as they are read";
|
|
41
|
+
}
|
|
42
|
+
function exportFormatOptions({ url, download }) {
|
|
43
|
+
if (!url) return [];
|
|
44
|
+
return getDownloadFormats({
|
|
45
|
+
url,
|
|
46
|
+
download
|
|
47
|
+
}).map((format) => {
|
|
48
|
+
const meta = getRemoteFormatMeta(format);
|
|
49
|
+
return {
|
|
50
|
+
format,
|
|
51
|
+
label: meta.label,
|
|
52
|
+
description: meta.description,
|
|
53
|
+
icon: meta.icon,
|
|
54
|
+
...meta.iconClassName ? { iconClassName: meta.iconClassName } : {}
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function exportScopeOptions(download, pagination) {
|
|
59
|
+
return getDownloadScopes(download).map((scope) => ({
|
|
60
|
+
scope,
|
|
61
|
+
label: scope === "all" || scope === void 0 ? allRowsLabel(pagination) : "Current page",
|
|
62
|
+
note: (format) => downloadDescription(format, scope, download)
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
function allRowsLabel(pagination) {
|
|
66
|
+
const total = pagination?.total;
|
|
67
|
+
if (total === void 0) return "All rows";
|
|
68
|
+
const count = total.toLocaleString();
|
|
69
|
+
return pagination?.totalRelation === "gte" ? `All ${count}+ rows` : `All ${count} rows`;
|
|
70
|
+
}
|
|
71
|
+
async function runClickyDownload({ url, format, scope, download }) {
|
|
72
|
+
let failure = "";
|
|
73
|
+
await prepareDownload({
|
|
74
|
+
url,
|
|
75
|
+
format,
|
|
76
|
+
...scope ? { scope } : {},
|
|
77
|
+
...download ? { download } : {},
|
|
78
|
+
onError: (message) => {
|
|
79
|
+
failure = message;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
if (failure) throw new Error(failure);
|
|
83
|
+
}
|
|
84
|
+
async function prepareDownload({ url, format, scope, download, onPending, onError }) {
|
|
85
|
+
onError?.("");
|
|
86
|
+
onPending?.(true);
|
|
87
|
+
try {
|
|
88
|
+
const prepared = download?.prepare ? await download.prepare({
|
|
89
|
+
format,
|
|
90
|
+
...scope ? { scope } : {}
|
|
91
|
+
}) : {
|
|
92
|
+
url,
|
|
93
|
+
...download?.label ? { label: download.label } : {}
|
|
94
|
+
};
|
|
95
|
+
if (!prepared.url.trim()) throw new Error("The prepared download URL is empty");
|
|
96
|
+
triggerDownload(buildDownloadUrl(prepared.url, format, prepared.label ?? download?.label, scope));
|
|
97
|
+
} catch (error) {
|
|
98
|
+
onError?.(error instanceof Error ? error.message : "Preparing the download failed");
|
|
99
|
+
} finally {
|
|
100
|
+
onPending?.(false);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function getRemoteFormatMeta(format) {
|
|
104
|
+
switch (format) {
|
|
105
|
+
case "clicky": return {
|
|
106
|
+
label: "Clicky",
|
|
107
|
+
description: "Rendered Clicky JSON with the rich Clicky viewer",
|
|
108
|
+
icon: require_UiEye.UiEye,
|
|
109
|
+
accept: "application/json+clicky, application/clicky+json;q=0.9, application/json;q=0.8,*/*;q=0.7"
|
|
110
|
+
};
|
|
111
|
+
case "json": return {
|
|
112
|
+
label: "JSON",
|
|
113
|
+
description: "Plain JSON for inspecting the raw response body",
|
|
114
|
+
icon: require_UiJson.UiJson,
|
|
115
|
+
accept: "application/json, text/plain;q=0.8,*/*;q=0.7"
|
|
116
|
+
};
|
|
117
|
+
case "ndjson": return {
|
|
118
|
+
label: "NDJSON",
|
|
119
|
+
description: "Newline-delimited JSON for streaming data pipelines",
|
|
120
|
+
icon: require_UiJson.UiJson,
|
|
121
|
+
accept: "application/x-ndjson, application/ndjson;q=0.9,*/*;q=0.7"
|
|
122
|
+
};
|
|
123
|
+
case "pdf": return {
|
|
124
|
+
label: "PDF",
|
|
125
|
+
description: "Portable document for sharing and printing",
|
|
126
|
+
icon: require_UiFileText.UiFileText,
|
|
127
|
+
iconClassName: "text-rose-600 dark:text-rose-400",
|
|
128
|
+
accept: "application/pdf, */*;q=0.7"
|
|
129
|
+
};
|
|
130
|
+
case "html": return {
|
|
131
|
+
label: "HTML",
|
|
132
|
+
description: "Browser-ready HTML preview of the formatted output",
|
|
133
|
+
icon: require_UiFileCode.UiFileCode,
|
|
134
|
+
accept: "text/html, */*;q=0.7"
|
|
135
|
+
};
|
|
136
|
+
case "markdown": return {
|
|
137
|
+
label: "Markdown",
|
|
138
|
+
description: "Markdown formatted for docs, comments, and chat",
|
|
139
|
+
icon: require_UiMarkdown.UiMarkdown,
|
|
140
|
+
accept: "text/markdown, text/plain;q=0.8,*/*;q=0.7"
|
|
141
|
+
};
|
|
142
|
+
case "yaml": return {
|
|
143
|
+
label: "YAML",
|
|
144
|
+
description: "YAML for config-friendly inspection and export",
|
|
145
|
+
icon: require_UiFileCode.UiFileCode,
|
|
146
|
+
iconClassName: "text-violet-600 dark:text-violet-400",
|
|
147
|
+
accept: "application/yaml, text/yaml;q=0.9, text/plain;q=0.8,*/*;q=0.7"
|
|
148
|
+
};
|
|
149
|
+
case "csv": return {
|
|
150
|
+
label: "CSV",
|
|
151
|
+
description: "Comma-separated values for spreadsheets and imports",
|
|
152
|
+
icon: require_UiFileSpreadsheet.UiFileSpreadsheet,
|
|
153
|
+
iconClassName: "text-emerald-600 dark:text-emerald-400",
|
|
154
|
+
accept: "text/csv, text/plain;q=0.8,*/*;q=0.7"
|
|
155
|
+
};
|
|
156
|
+
case "pretty": return {
|
|
157
|
+
label: "Pretty",
|
|
158
|
+
description: "Human-readable plain text output from the formatter",
|
|
159
|
+
icon: require_UiFileCode.UiFileCode,
|
|
160
|
+
accept: "text/plain, */*;q=0.7"
|
|
161
|
+
};
|
|
162
|
+
case "excel": return {
|
|
163
|
+
label: "Excel",
|
|
164
|
+
description: "Spreadsheet workbook for offline analysis",
|
|
165
|
+
icon: require_UiTable.UiTable,
|
|
166
|
+
accept: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, */*;q=0.7"
|
|
167
|
+
};
|
|
168
|
+
case "slack": return {
|
|
169
|
+
label: "Slack",
|
|
170
|
+
description: "Slack Block Kit JSON for chat-native output",
|
|
171
|
+
icon: require_UiComment.UiComment,
|
|
172
|
+
accept: "application/vnd.slack.block-kit+json, application/json;q=0.8,*/*;q=0.7"
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function buildFormatUrl(url, format) {
|
|
177
|
+
const base = typeof window === "undefined" ? "http://localhost" : window.location.origin;
|
|
178
|
+
const resolved = new URL(url, base);
|
|
179
|
+
resolved.searchParams.set("format", format === "clicky" ? "clicky-json" : format);
|
|
180
|
+
if (isAbsoluteUrl(url)) return resolved.toString();
|
|
181
|
+
return `${resolved.pathname}${resolved.search}${resolved.hash}`;
|
|
182
|
+
}
|
|
183
|
+
function buildDownloadUrl(url, format, label, scope) {
|
|
184
|
+
const formatted = buildFormatUrl(url, format);
|
|
185
|
+
const base = typeof window === "undefined" ? "http://localhost" : window.location.origin;
|
|
186
|
+
const resolved = new URL(formatted, base);
|
|
187
|
+
resolved.searchParams.set("filename", downloadFilename(url, format, label));
|
|
188
|
+
resolved.searchParams.set("_download", Date.now().toString());
|
|
189
|
+
if (scope) resolved.searchParams.set("scope", scope);
|
|
190
|
+
if (scope === "all") {
|
|
191
|
+
resolved.searchParams.delete("limit");
|
|
192
|
+
resolved.searchParams.delete("offset");
|
|
193
|
+
resolved.searchParams.delete("page");
|
|
194
|
+
}
|
|
195
|
+
if (isAbsoluteUrl(formatted)) return resolved.toString();
|
|
196
|
+
return `${resolved.pathname}${resolved.search}${resolved.hash}`;
|
|
197
|
+
}
|
|
198
|
+
function downloadFilename(url, format, label) {
|
|
199
|
+
return `${sanitizeFilenameStem(label || filenameStemFromUrl(url) || "clicky-output")}${downloadExtension(format)}`;
|
|
200
|
+
}
|
|
201
|
+
function filenameStemFromUrl(url) {
|
|
202
|
+
const base = typeof window === "undefined" ? "http://localhost" : window.location.origin;
|
|
203
|
+
return new URL(url, base).pathname.split("/").filter(Boolean).at(-1) ?? "";
|
|
204
|
+
}
|
|
205
|
+
function sanitizeFilenameStem(value) {
|
|
206
|
+
return require_string.stripSurroundingChars(value.trim().replace(/\.[a-z0-9]+$/i, "").replace(/[^a-z0-9._-]+/gi, "-").replace(/-+/g, "-"), "._-") || "clicky-output";
|
|
207
|
+
}
|
|
208
|
+
function downloadExtension(format) {
|
|
209
|
+
switch (format) {
|
|
210
|
+
case "clicky": return ".clicky.json";
|
|
211
|
+
case "json": return ".json";
|
|
212
|
+
case "ndjson": return ".ndjson";
|
|
213
|
+
case "pdf": return ".pdf";
|
|
214
|
+
case "html": return ".html";
|
|
215
|
+
case "markdown": return ".md";
|
|
216
|
+
case "yaml": return ".yaml";
|
|
217
|
+
case "csv": return ".csv";
|
|
218
|
+
case "pretty": return ".txt";
|
|
219
|
+
case "excel": return ".xlsx";
|
|
220
|
+
case "slack": return ".slack.json";
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
var CLICKY_DOWNLOAD_FRAME_ID = "clicky-download-frame";
|
|
224
|
+
function triggerDownload(url) {
|
|
225
|
+
if (typeof document === "undefined") return;
|
|
226
|
+
let frame = document.getElementById(CLICKY_DOWNLOAD_FRAME_ID);
|
|
227
|
+
if (!frame) {
|
|
228
|
+
frame = document.createElement("iframe");
|
|
229
|
+
frame.id = CLICKY_DOWNLOAD_FRAME_ID;
|
|
230
|
+
frame.name = CLICKY_DOWNLOAD_FRAME_ID;
|
|
231
|
+
frame.title = "Clicky download target";
|
|
232
|
+
frame.setAttribute("aria-hidden", "true");
|
|
233
|
+
frame.tabIndex = -1;
|
|
234
|
+
frame.style.display = "none";
|
|
235
|
+
document.body.appendChild(frame);
|
|
236
|
+
}
|
|
237
|
+
frame.src = url;
|
|
238
|
+
}
|
|
239
|
+
function isAbsoluteUrl(url) {
|
|
240
|
+
return /^[a-z][a-z\d+\-.]*:/i.test(url) || url.startsWith("//");
|
|
241
|
+
}
|
|
242
|
+
exports.CLICKY_OVERFLOW_VIEW_FORMATS = CLICKY_OVERFLOW_VIEW_FORMATS;
|
|
243
|
+
exports.CLICKY_PRIMARY_VIEW_FORMATS = CLICKY_PRIMARY_VIEW_FORMATS;
|
|
244
|
+
exports.buildFormatUrl = buildFormatUrl;
|
|
245
|
+
exports.exportFormatOptions = exportFormatOptions;
|
|
246
|
+
exports.exportScopeOptions = exportScopeOptions;
|
|
247
|
+
exports.getDownloadFormats = getDownloadFormats;
|
|
248
|
+
exports.getRemoteFormatMeta = getRemoteFormatMeta;
|
|
249
|
+
exports.runClickyDownload = runClickyDownload;
|
|
250
|
+
|
|
251
|
+
//# sourceMappingURL=clicky-download.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clicky-download.cjs","names":["UiEye","UiJson","UiFileText","UiFileCode","UiMarkdown","UiFileSpreadsheet","UiTable","UiComment","stripSurroundingChars"],"sources":["../../src/data/clicky-download.ts"],"sourcesContent":["import type { StaticIconComponent } from \"./Icon\";\nimport {\n UiComment,\n UiEye,\n UiFileCode,\n UiFileSpreadsheet,\n UiFileText,\n UiJson,\n UiMarkdown,\n UiTable,\n} from \"../icons\";\nimport type { DataTablePagination } from \"./DataTable\";\nimport type {\n ClickyExportFormatOption,\n ClickyExportScopeOption,\n} from \"./clicky-export/ClickyExportDialog\";\nimport { stripSurroundingChars } from \"../lib/string\";\n\n// Remote format and download helpers shared by the Clicky viewer and\n// useClickyTableMenu. Kept out of Clicky.tsx so the hook can be exported without\n// breaking fast refresh (react/only-export-components).\n\nexport const CLICKY_PRIMARY_VIEW_FORMATS = [\"clicky\", \"json\"] as const;\nexport const CLICKY_OVERFLOW_VIEW_FORMATS = [\n \"pdf\",\n \"html\",\n \"markdown\",\n \"yaml\",\n \"csv\",\n \"pretty\",\n \"excel\",\n \"slack\",\n] as const;\nconst CLICKY_DOWNLOAD_FORMATS = [\n \"yaml\",\n \"json\",\n \"csv\",\n \"pdf\",\n \"markdown\",\n] as const;\n\nexport type ClickyPrimaryViewFormat =\n (typeof CLICKY_PRIMARY_VIEW_FORMATS)[number];\nexport type ClickyOverflowViewFormat =\n (typeof CLICKY_OVERFLOW_VIEW_FORMATS)[number];\nexport type ClickyRemoteFormat =\n | ClickyPrimaryViewFormat\n | ClickyOverflowViewFormat\n | \"ndjson\";\n\nexport type ClickyDownloadOptions = {\n /** Show all advertised download formats in the overflow menu. */\n all?: boolean;\n /** Override the download menu label. */\n label?: string;\n /** Formats advertised by the endpoint. Defaults to Clicky's legacy set. */\n formats?: ClickyRemoteFormat[];\n /** Result ranges advertised by the endpoint. Legacy endpoints omit this. */\n scopes?: ClickyDownloadScope[];\n /** Whether all-row export streams or uses a compatibility buffer. */\n allRowsMode?: \"streaming\" | \"buffered\";\n /** Per-format server row caps, for example `{ pdf: 1000 }`. */\n formatMaxRows?: Partial<Record<ClickyRemoteFormat, number>>;\n /** Prepare an export-specific resource before the native download starts. */\n prepare?: (request: {\n format: ClickyRemoteFormat;\n scope?: ClickyDownloadScope;\n }) => Promise<{ url: string; label?: string }>;\n};\n\nexport type ClickyDownloadScope = \"page\" | \"all\";\n\nexport function getDownloadFormats({\n url,\n download,\n}: {\n url?: string | undefined;\n download?: ClickyDownloadOptions | undefined;\n}): ClickyRemoteFormat[] {\n const enabled = download ? (download.all ?? true) : !!url;\n\n if (!enabled || !url) {\n return [];\n }\n\n return download?.formats?.length\n ? [...download.formats]\n : [...CLICKY_DOWNLOAD_FORMATS];\n}\n\nfunction getDownloadScopes(\n download?: ClickyDownloadOptions,\n): Array<ClickyDownloadScope | undefined> {\n return download?.scopes?.length ? download.scopes : [undefined];\n}\n\nfunction downloadDescription(\n format: ClickyRemoteFormat,\n scope: ClickyDownloadScope | undefined,\n download?: ClickyDownloadOptions,\n) {\n const maxRows = download?.formatMaxRows?.[format];\n if (maxRows) {\n return `Limited to ${maxRows.toLocaleString()} rows`;\n }\n if (scope === \"all\" && download?.allRowsMode === \"buffered\") {\n return \"Compatibility mode buffers the complete result\";\n }\n if (scope === \"all\" && download?.allRowsMode === \"streaming\") {\n return \"Streams rows as they are read\";\n }\n return undefined;\n}\n\n// exportFormatOptions describes the advertised formats for the export dialog.\n// The dialog is presentational and knows nothing about Clicky's formats, so the\n// labels, icons and blurbs are resolved here, from the same metadata the view\n// switcher reads.\nexport function exportFormatOptions({\n url,\n download,\n}: {\n url?: string | undefined;\n download?: ClickyDownloadOptions | undefined;\n}): ClickyExportFormatOption[] {\n if (!url) return [];\n return getDownloadFormats({ url, download }).map((format) => {\n const meta = getRemoteFormatMeta(format);\n return {\n format,\n label: meta.label,\n description: meta.description,\n icon: meta.icon,\n ...(meta.iconClassName ? { iconClassName: meta.iconClassName } : {}),\n };\n });\n}\n\n// exportScopeOptions describes the ranges the endpoint serves. The per-format\n// note is a function rather than a string because a ceiling belongs to a format\n// and a range together: a PDF stops at 1,000 rows, the CSV beside it does not.\n//\n// The all-rows label carries the count the table was told, because \"all\" is the\n// one number a person needs before starting an export and the only one the page\n// in front of them does not show.\nexport function exportScopeOptions(\n download?: ClickyDownloadOptions,\n pagination?: DataTablePagination,\n): ClickyExportScopeOption[] {\n return getDownloadScopes(download).map((scope) => ({\n scope,\n label:\n scope === \"all\" || scope === undefined\n ? allRowsLabel(pagination)\n : \"Current page\",\n note: (format: string) =>\n downloadDescription(format as ClickyRemoteFormat, scope, download),\n }));\n}\n\n// allRowsLabel states the total when the backend gave one. A \"gte\" total is a\n// lower bound a backend stopped counting at, so it is rendered as one rather\n// than as a count nobody promised.\nfunction allRowsLabel(pagination?: DataTablePagination): string {\n const total = pagination?.total;\n if (total === undefined) return \"All rows\";\n const count = total.toLocaleString();\n return pagination?.totalRelation === \"gte\"\n ? `All ${count}+ rows`\n : `All ${count} rows`;\n}\n\n// runClickyDownload is prepareDownload for a caller that awaits it. The export\n// dialog reports its own failure and stays open on one, so the reason has to\n// come back as a rejection rather than through a callback.\nexport async function runClickyDownload({\n url,\n format,\n scope,\n download,\n}: {\n url: string;\n format: ClickyRemoteFormat;\n scope?: ClickyDownloadScope | undefined;\n download?: ClickyDownloadOptions | undefined;\n}): Promise<void> {\n let failure = \"\";\n await prepareDownload({\n url,\n format,\n ...(scope ? { scope } : {}),\n ...(download ? { download } : {}),\n onError: (message) => {\n failure = message;\n },\n });\n if (failure) throw new Error(failure);\n}\n\nasync function prepareDownload({\n url,\n format,\n scope,\n download,\n onPending,\n onError,\n}: {\n url: string;\n format: ClickyRemoteFormat;\n scope?: ClickyDownloadScope | undefined;\n download?: ClickyDownloadOptions | undefined;\n onPending?: ((pending: boolean) => void) | undefined;\n onError?: ((message: string) => void) | undefined;\n}) {\n onError?.(\"\");\n onPending?.(true);\n try {\n const prepared = download?.prepare\n ? await download.prepare({ format, ...(scope ? { scope } : {}) })\n : { url, ...(download?.label ? { label: download.label } : {}) };\n if (!prepared.url.trim()) {\n throw new Error(\"The prepared download URL is empty\");\n }\n triggerDownload(\n buildDownloadUrl(\n prepared.url,\n format,\n prepared.label ?? download?.label,\n scope,\n ),\n );\n } catch (error) {\n onError?.(\n error instanceof Error ? error.message : \"Preparing the download failed\",\n );\n } finally {\n onPending?.(false);\n }\n}\n\nexport function getRemoteFormatMeta(format: ClickyRemoteFormat): {\n label: string;\n description: string;\n icon: StaticIconComponent;\n iconClassName?: string;\n accept: string;\n} {\n switch (format) {\n case \"clicky\":\n return {\n label: \"Clicky\",\n description: \"Rendered Clicky JSON with the rich Clicky viewer\",\n icon: UiEye,\n accept:\n \"application/json+clicky, application/clicky+json;q=0.9, application/json;q=0.8,*/*;q=0.7\",\n };\n case \"json\":\n return {\n label: \"JSON\",\n description: \"Plain JSON for inspecting the raw response body\",\n icon: UiJson,\n accept: \"application/json, text/plain;q=0.8,*/*;q=0.7\",\n };\n case \"ndjson\":\n return {\n label: \"NDJSON\",\n description: \"Newline-delimited JSON for streaming data pipelines\",\n icon: UiJson,\n accept: \"application/x-ndjson, application/ndjson;q=0.9,*/*;q=0.7\",\n };\n case \"pdf\":\n return {\n label: \"PDF\",\n description: \"Portable document for sharing and printing\",\n icon: UiFileText,\n iconClassName: \"text-rose-600 dark:text-rose-400\",\n accept: \"application/pdf, */*;q=0.7\",\n };\n case \"html\":\n return {\n label: \"HTML\",\n description: \"Browser-ready HTML preview of the formatted output\",\n icon: UiFileCode,\n accept: \"text/html, */*;q=0.7\",\n };\n case \"markdown\":\n return {\n label: \"Markdown\",\n description: \"Markdown formatted for docs, comments, and chat\",\n icon: UiMarkdown,\n accept: \"text/markdown, text/plain;q=0.8,*/*;q=0.7\",\n };\n case \"yaml\":\n return {\n label: \"YAML\",\n description: \"YAML for config-friendly inspection and export\",\n icon: UiFileCode,\n iconClassName: \"text-violet-600 dark:text-violet-400\",\n accept: \"application/yaml, text/yaml;q=0.9, text/plain;q=0.8,*/*;q=0.7\",\n };\n case \"csv\":\n return {\n label: \"CSV\",\n description: \"Comma-separated values for spreadsheets and imports\",\n icon: UiFileSpreadsheet,\n iconClassName: \"text-emerald-600 dark:text-emerald-400\",\n accept: \"text/csv, text/plain;q=0.8,*/*;q=0.7\",\n };\n case \"pretty\":\n return {\n label: \"Pretty\",\n description: \"Human-readable plain text output from the formatter\",\n icon: UiFileCode,\n accept: \"text/plain, */*;q=0.7\",\n };\n case \"excel\":\n return {\n label: \"Excel\",\n description: \"Spreadsheet workbook for offline analysis\",\n icon: UiTable,\n accept:\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, */*;q=0.7\",\n };\n case \"slack\":\n return {\n label: \"Slack\",\n description: \"Slack Block Kit JSON for chat-native output\",\n icon: UiComment,\n accept:\n \"application/vnd.slack.block-kit+json, application/json;q=0.8,*/*;q=0.7\",\n };\n }\n}\n\nexport function buildFormatUrl(url: string, format: ClickyRemoteFormat) {\n const base =\n typeof window === \"undefined\" ? \"http://localhost\" : window.location.origin;\n const resolved = new URL(url, base);\n resolved.searchParams.set(\n \"format\",\n format === \"clicky\" ? \"clicky-json\" : format,\n );\n\n if (isAbsoluteUrl(url)) {\n return resolved.toString();\n }\n\n return `${resolved.pathname}${resolved.search}${resolved.hash}`;\n}\n\nfunction buildDownloadUrl(\n url: string,\n format: ClickyRemoteFormat,\n label?: string | undefined,\n scope?: ClickyDownloadScope | undefined,\n) {\n const formatted = buildFormatUrl(url, format);\n const base =\n typeof window === \"undefined\" ? \"http://localhost\" : window.location.origin;\n const resolved = new URL(formatted, base);\n resolved.searchParams.set(\"filename\", downloadFilename(url, format, label));\n resolved.searchParams.set(\"_download\", Date.now().toString());\n if (scope) {\n resolved.searchParams.set(\"scope\", scope);\n }\n if (scope === \"all\") {\n resolved.searchParams.delete(\"limit\");\n resolved.searchParams.delete(\"offset\");\n resolved.searchParams.delete(\"page\");\n }\n\n if (isAbsoluteUrl(formatted)) {\n return resolved.toString();\n }\n\n return `${resolved.pathname}${resolved.search}${resolved.hash}`;\n}\n\nfunction downloadFilename(\n url: string,\n format: ClickyRemoteFormat,\n label?: string | undefined,\n) {\n const stem = sanitizeFilenameStem(\n label || filenameStemFromUrl(url) || \"clicky-output\",\n );\n return `${stem}${downloadExtension(format)}`;\n}\n\nfunction filenameStemFromUrl(url: string) {\n const base =\n typeof window === \"undefined\" ? \"http://localhost\" : window.location.origin;\n const resolved = new URL(url, base);\n const segment = resolved.pathname.split(\"/\").filter(Boolean).at(-1);\n return segment ?? \"\";\n}\n\nfunction sanitizeFilenameStem(value: string) {\n const cleaned = stripSurroundingChars(\n value\n .trim()\n .replace(/\\.[a-z0-9]+$/i, \"\")\n .replace(/[^a-z0-9._-]+/gi, \"-\")\n .replace(/-+/g, \"-\"),\n \"._-\",\n );\n\n return cleaned || \"clicky-output\";\n}\n\nfunction downloadExtension(format: ClickyRemoteFormat) {\n switch (format) {\n case \"clicky\":\n return \".clicky.json\";\n case \"json\":\n return \".json\";\n case \"ndjson\":\n return \".ndjson\";\n case \"pdf\":\n return \".pdf\";\n case \"html\":\n return \".html\";\n case \"markdown\":\n return \".md\";\n case \"yaml\":\n return \".yaml\";\n case \"csv\":\n return \".csv\";\n case \"pretty\":\n return \".txt\";\n case \"excel\":\n return \".xlsx\";\n case \"slack\":\n return \".slack.json\";\n }\n}\n\nconst CLICKY_DOWNLOAD_FRAME_ID = \"clicky-download-frame\";\n\nfunction triggerDownload(url: string) {\n if (typeof document === \"undefined\") return;\n\n let frame = document.getElementById(\n CLICKY_DOWNLOAD_FRAME_ID,\n ) as HTMLIFrameElement | null;\n if (!frame) {\n frame = document.createElement(\"iframe\");\n frame.id = CLICKY_DOWNLOAD_FRAME_ID;\n frame.name = CLICKY_DOWNLOAD_FRAME_ID;\n frame.title = \"Clicky download target\";\n frame.setAttribute(\"aria-hidden\", \"true\");\n frame.tabIndex = -1;\n frame.style.display = \"none\";\n document.body.appendChild(frame);\n }\n\n frame.src = url;\n}\n\nfunction isAbsoluteUrl(url: string) {\n return /^[a-z][a-z\\d+\\-.]*:/i.test(url) || url.startsWith(\"//\");\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { StaticIconComponent } from './Icon.js';
|
|
2
|
+
import { DataTablePagination } from './DataTable.js';
|
|
3
|
+
import { ClickyExportFormatOption, ClickyExportScopeOption } from './clicky-export/ClickyExportDialog.js';
|
|
4
|
+
export declare const CLICKY_PRIMARY_VIEW_FORMATS: readonly ["clicky", "json"];
|
|
5
|
+
export declare const CLICKY_OVERFLOW_VIEW_FORMATS: readonly ["pdf", "html", "markdown", "yaml", "csv", "pretty", "excel", "slack"];
|
|
6
|
+
export type ClickyPrimaryViewFormat = (typeof CLICKY_PRIMARY_VIEW_FORMATS)[number];
|
|
7
|
+
export type ClickyOverflowViewFormat = (typeof CLICKY_OVERFLOW_VIEW_FORMATS)[number];
|
|
8
|
+
export type ClickyRemoteFormat = ClickyPrimaryViewFormat | ClickyOverflowViewFormat | "ndjson";
|
|
9
|
+
export type ClickyDownloadOptions = {
|
|
10
|
+
/** Show all advertised download formats in the overflow menu. */
|
|
11
|
+
all?: boolean;
|
|
12
|
+
/** Override the download menu label. */
|
|
13
|
+
label?: string;
|
|
14
|
+
/** Formats advertised by the endpoint. Defaults to Clicky's legacy set. */
|
|
15
|
+
formats?: ClickyRemoteFormat[];
|
|
16
|
+
/** Result ranges advertised by the endpoint. Legacy endpoints omit this. */
|
|
17
|
+
scopes?: ClickyDownloadScope[];
|
|
18
|
+
/** Whether all-row export streams or uses a compatibility buffer. */
|
|
19
|
+
allRowsMode?: "streaming" | "buffered";
|
|
20
|
+
/** Per-format server row caps, for example `{ pdf: 1000 }`. */
|
|
21
|
+
formatMaxRows?: Partial<Record<ClickyRemoteFormat, number>>;
|
|
22
|
+
/** Prepare an export-specific resource before the native download starts. */
|
|
23
|
+
prepare?: (request: {
|
|
24
|
+
format: ClickyRemoteFormat;
|
|
25
|
+
scope?: ClickyDownloadScope;
|
|
26
|
+
}) => Promise<{
|
|
27
|
+
url: string;
|
|
28
|
+
label?: string;
|
|
29
|
+
}>;
|
|
30
|
+
};
|
|
31
|
+
export type ClickyDownloadScope = "page" | "all";
|
|
32
|
+
export declare function getDownloadFormats({ url, download, }: {
|
|
33
|
+
url?: string | undefined;
|
|
34
|
+
download?: ClickyDownloadOptions | undefined;
|
|
35
|
+
}): ClickyRemoteFormat[];
|
|
36
|
+
export declare function exportFormatOptions({ url, download, }: {
|
|
37
|
+
url?: string | undefined;
|
|
38
|
+
download?: ClickyDownloadOptions | undefined;
|
|
39
|
+
}): ClickyExportFormatOption[];
|
|
40
|
+
export declare function exportScopeOptions(download?: ClickyDownloadOptions, pagination?: DataTablePagination): ClickyExportScopeOption[];
|
|
41
|
+
export declare function runClickyDownload({ url, format, scope, download, }: {
|
|
42
|
+
url: string;
|
|
43
|
+
format: ClickyRemoteFormat;
|
|
44
|
+
scope?: ClickyDownloadScope | undefined;
|
|
45
|
+
download?: ClickyDownloadOptions | undefined;
|
|
46
|
+
}): Promise<void>;
|
|
47
|
+
export declare function getRemoteFormatMeta(format: ClickyRemoteFormat): {
|
|
48
|
+
label: string;
|
|
49
|
+
description: string;
|
|
50
|
+
icon: StaticIconComponent;
|
|
51
|
+
iconClassName?: string;
|
|
52
|
+
accept: string;
|
|
53
|
+
};
|
|
54
|
+
export declare function buildFormatUrl(url: string, format: ClickyRemoteFormat): string;
|
|
55
|
+
//# sourceMappingURL=clicky-download.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clicky-download.d.ts","sourceRoot":"","sources":["../../src/data/clicky-download.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAWlD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EACV,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,oCAAoC,CAAC;AAO5C,eAAO,MAAM,2BAA2B,6BAA8B,CAAC;AACvE,eAAO,MAAM,4BAA4B,iFAS/B,CAAC;AASX,MAAM,MAAM,uBAAuB,GACjC,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AAC/C,MAAM,MAAM,wBAAwB,GAClC,CAAC,OAAO,4BAA4B,CAAC,CAAC,MAAM,CAAC,CAAC;AAChD,MAAM,MAAM,kBAAkB,GAC1B,uBAAuB,GACvB,wBAAwB,GACxB,QAAQ,CAAC;AAEb,MAAM,MAAM,qBAAqB,GAAG;IAClC,iEAAiE;IACjE,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC/B,4EAA4E;IAC5E,MAAM,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAC/B,qEAAqE;IACrE,WAAW,CAAC,EAAE,WAAW,GAAG,UAAU,CAAC;IACvC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5D,6EAA6E;IAC7E,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE;QAClB,MAAM,EAAE,kBAAkB,CAAC;QAC3B,KAAK,CAAC,EAAE,mBAAmB,CAAC;KAC7B,KAAK,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,KAAK,CAAC;AAEjD,wBAAgB,kBAAkB,CAAC,EACjC,GAAG,EACH,QAAQ,GACT,EAAE;IACD,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,QAAQ,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;CAC9C,GAAG,kBAAkB,EAAE,CAUvB;AA8BD,wBAAgB,mBAAmB,CAAC,EAClC,GAAG,EACH,QAAQ,GACT,EAAE;IACD,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,QAAQ,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;CAC9C,GAAG,wBAAwB,EAAE,CAY7B;AASD,wBAAgB,kBAAkB,CAChC,QAAQ,CAAC,EAAE,qBAAqB,EAChC,UAAU,CAAC,EAAE,mBAAmB,GAC/B,uBAAuB,EAAE,CAU3B;AAiBD,wBAAsB,iBAAiB,CAAC,EACtC,GAAG,EACH,MAAM,EACN,KAAK,EACL,QAAQ,GACT,EAAE;IACD,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,kBAAkB,CAAC;IAC3B,KAAK,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;CAC9C,GAAG,OAAO,CAAC,IAAI,CAAC,CAYhB;AA2CD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,GAAG;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,mBAAmB,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB,CAsFA;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,UAcrE"}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { UiEye } from "../icons/components/UiEye.js";
|
|
2
|
+
import { UiTable } from "../icons/components/UiTable.js";
|
|
3
|
+
import { UiComment } from "../icons/components/UiComment.js";
|
|
4
|
+
import { UiFileCode } from "../icons/components/UiFileCode.js";
|
|
5
|
+
import { UiJson } from "../icons/components/UiJson.js";
|
|
6
|
+
import { UiMarkdown } from "../icons/components/UiMarkdown.js";
|
|
7
|
+
import { UiFileText } from "../icons/components/UiFileText.js";
|
|
8
|
+
import { UiFileSpreadsheet } from "../icons/components/UiFileSpreadsheet.js";
|
|
9
|
+
import { stripSurroundingChars } from "../lib/string.js";
|
|
10
|
+
// src/data/clicky-download.ts
|
|
11
|
+
var CLICKY_PRIMARY_VIEW_FORMATS = ["clicky", "json"];
|
|
12
|
+
var CLICKY_OVERFLOW_VIEW_FORMATS = [
|
|
13
|
+
"pdf",
|
|
14
|
+
"html",
|
|
15
|
+
"markdown",
|
|
16
|
+
"yaml",
|
|
17
|
+
"csv",
|
|
18
|
+
"pretty",
|
|
19
|
+
"excel",
|
|
20
|
+
"slack"
|
|
21
|
+
];
|
|
22
|
+
var CLICKY_DOWNLOAD_FORMATS = [
|
|
23
|
+
"yaml",
|
|
24
|
+
"json",
|
|
25
|
+
"csv",
|
|
26
|
+
"pdf",
|
|
27
|
+
"markdown"
|
|
28
|
+
];
|
|
29
|
+
function getDownloadFormats({ url, download }) {
|
|
30
|
+
if (!(download ? download.all ?? true : !!url) || !url) return [];
|
|
31
|
+
return download?.formats?.length ? [...download.formats] : [...CLICKY_DOWNLOAD_FORMATS];
|
|
32
|
+
}
|
|
33
|
+
function getDownloadScopes(download) {
|
|
34
|
+
return download?.scopes?.length ? download.scopes : [void 0];
|
|
35
|
+
}
|
|
36
|
+
function downloadDescription(format, scope, download) {
|
|
37
|
+
const maxRows = download?.formatMaxRows?.[format];
|
|
38
|
+
if (maxRows) return `Limited to ${maxRows.toLocaleString()} rows`;
|
|
39
|
+
if (scope === "all" && download?.allRowsMode === "buffered") return "Compatibility mode buffers the complete result";
|
|
40
|
+
if (scope === "all" && download?.allRowsMode === "streaming") return "Streams rows as they are read";
|
|
41
|
+
}
|
|
42
|
+
function exportFormatOptions({ url, download }) {
|
|
43
|
+
if (!url) return [];
|
|
44
|
+
return getDownloadFormats({
|
|
45
|
+
url,
|
|
46
|
+
download
|
|
47
|
+
}).map((format) => {
|
|
48
|
+
const meta = getRemoteFormatMeta(format);
|
|
49
|
+
return {
|
|
50
|
+
format,
|
|
51
|
+
label: meta.label,
|
|
52
|
+
description: meta.description,
|
|
53
|
+
icon: meta.icon,
|
|
54
|
+
...meta.iconClassName ? { iconClassName: meta.iconClassName } : {}
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function exportScopeOptions(download, pagination) {
|
|
59
|
+
return getDownloadScopes(download).map((scope) => ({
|
|
60
|
+
scope,
|
|
61
|
+
label: scope === "all" || scope === void 0 ? allRowsLabel(pagination) : "Current page",
|
|
62
|
+
note: (format) => downloadDescription(format, scope, download)
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
function allRowsLabel(pagination) {
|
|
66
|
+
const total = pagination?.total;
|
|
67
|
+
if (total === void 0) return "All rows";
|
|
68
|
+
const count = total.toLocaleString();
|
|
69
|
+
return pagination?.totalRelation === "gte" ? `All ${count}+ rows` : `All ${count} rows`;
|
|
70
|
+
}
|
|
71
|
+
async function runClickyDownload({ url, format, scope, download }) {
|
|
72
|
+
let failure = "";
|
|
73
|
+
await prepareDownload({
|
|
74
|
+
url,
|
|
75
|
+
format,
|
|
76
|
+
...scope ? { scope } : {},
|
|
77
|
+
...download ? { download } : {},
|
|
78
|
+
onError: (message) => {
|
|
79
|
+
failure = message;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
if (failure) throw new Error(failure);
|
|
83
|
+
}
|
|
84
|
+
async function prepareDownload({ url, format, scope, download, onPending, onError }) {
|
|
85
|
+
onError?.("");
|
|
86
|
+
onPending?.(true);
|
|
87
|
+
try {
|
|
88
|
+
const prepared = download?.prepare ? await download.prepare({
|
|
89
|
+
format,
|
|
90
|
+
...scope ? { scope } : {}
|
|
91
|
+
}) : {
|
|
92
|
+
url,
|
|
93
|
+
...download?.label ? { label: download.label } : {}
|
|
94
|
+
};
|
|
95
|
+
if (!prepared.url.trim()) throw new Error("The prepared download URL is empty");
|
|
96
|
+
triggerDownload(buildDownloadUrl(prepared.url, format, prepared.label ?? download?.label, scope));
|
|
97
|
+
} catch (error) {
|
|
98
|
+
onError?.(error instanceof Error ? error.message : "Preparing the download failed");
|
|
99
|
+
} finally {
|
|
100
|
+
onPending?.(false);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function getRemoteFormatMeta(format) {
|
|
104
|
+
switch (format) {
|
|
105
|
+
case "clicky": return {
|
|
106
|
+
label: "Clicky",
|
|
107
|
+
description: "Rendered Clicky JSON with the rich Clicky viewer",
|
|
108
|
+
icon: UiEye,
|
|
109
|
+
accept: "application/json+clicky, application/clicky+json;q=0.9, application/json;q=0.8,*/*;q=0.7"
|
|
110
|
+
};
|
|
111
|
+
case "json": return {
|
|
112
|
+
label: "JSON",
|
|
113
|
+
description: "Plain JSON for inspecting the raw response body",
|
|
114
|
+
icon: UiJson,
|
|
115
|
+
accept: "application/json, text/plain;q=0.8,*/*;q=0.7"
|
|
116
|
+
};
|
|
117
|
+
case "ndjson": return {
|
|
118
|
+
label: "NDJSON",
|
|
119
|
+
description: "Newline-delimited JSON for streaming data pipelines",
|
|
120
|
+
icon: UiJson,
|
|
121
|
+
accept: "application/x-ndjson, application/ndjson;q=0.9,*/*;q=0.7"
|
|
122
|
+
};
|
|
123
|
+
case "pdf": return {
|
|
124
|
+
label: "PDF",
|
|
125
|
+
description: "Portable document for sharing and printing",
|
|
126
|
+
icon: UiFileText,
|
|
127
|
+
iconClassName: "text-rose-600 dark:text-rose-400",
|
|
128
|
+
accept: "application/pdf, */*;q=0.7"
|
|
129
|
+
};
|
|
130
|
+
case "html": return {
|
|
131
|
+
label: "HTML",
|
|
132
|
+
description: "Browser-ready HTML preview of the formatted output",
|
|
133
|
+
icon: UiFileCode,
|
|
134
|
+
accept: "text/html, */*;q=0.7"
|
|
135
|
+
};
|
|
136
|
+
case "markdown": return {
|
|
137
|
+
label: "Markdown",
|
|
138
|
+
description: "Markdown formatted for docs, comments, and chat",
|
|
139
|
+
icon: UiMarkdown,
|
|
140
|
+
accept: "text/markdown, text/plain;q=0.8,*/*;q=0.7"
|
|
141
|
+
};
|
|
142
|
+
case "yaml": return {
|
|
143
|
+
label: "YAML",
|
|
144
|
+
description: "YAML for config-friendly inspection and export",
|
|
145
|
+
icon: UiFileCode,
|
|
146
|
+
iconClassName: "text-violet-600 dark:text-violet-400",
|
|
147
|
+
accept: "application/yaml, text/yaml;q=0.9, text/plain;q=0.8,*/*;q=0.7"
|
|
148
|
+
};
|
|
149
|
+
case "csv": return {
|
|
150
|
+
label: "CSV",
|
|
151
|
+
description: "Comma-separated values for spreadsheets and imports",
|
|
152
|
+
icon: UiFileSpreadsheet,
|
|
153
|
+
iconClassName: "text-emerald-600 dark:text-emerald-400",
|
|
154
|
+
accept: "text/csv, text/plain;q=0.8,*/*;q=0.7"
|
|
155
|
+
};
|
|
156
|
+
case "pretty": return {
|
|
157
|
+
label: "Pretty",
|
|
158
|
+
description: "Human-readable plain text output from the formatter",
|
|
159
|
+
icon: UiFileCode,
|
|
160
|
+
accept: "text/plain, */*;q=0.7"
|
|
161
|
+
};
|
|
162
|
+
case "excel": return {
|
|
163
|
+
label: "Excel",
|
|
164
|
+
description: "Spreadsheet workbook for offline analysis",
|
|
165
|
+
icon: UiTable,
|
|
166
|
+
accept: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, */*;q=0.7"
|
|
167
|
+
};
|
|
168
|
+
case "slack": return {
|
|
169
|
+
label: "Slack",
|
|
170
|
+
description: "Slack Block Kit JSON for chat-native output",
|
|
171
|
+
icon: UiComment,
|
|
172
|
+
accept: "application/vnd.slack.block-kit+json, application/json;q=0.8,*/*;q=0.7"
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function buildFormatUrl(url, format) {
|
|
177
|
+
const base = typeof window === "undefined" ? "http://localhost" : window.location.origin;
|
|
178
|
+
const resolved = new URL(url, base);
|
|
179
|
+
resolved.searchParams.set("format", format === "clicky" ? "clicky-json" : format);
|
|
180
|
+
if (isAbsoluteUrl(url)) return resolved.toString();
|
|
181
|
+
return `${resolved.pathname}${resolved.search}${resolved.hash}`;
|
|
182
|
+
}
|
|
183
|
+
function buildDownloadUrl(url, format, label, scope) {
|
|
184
|
+
const formatted = buildFormatUrl(url, format);
|
|
185
|
+
const base = typeof window === "undefined" ? "http://localhost" : window.location.origin;
|
|
186
|
+
const resolved = new URL(formatted, base);
|
|
187
|
+
resolved.searchParams.set("filename", downloadFilename(url, format, label));
|
|
188
|
+
resolved.searchParams.set("_download", Date.now().toString());
|
|
189
|
+
if (scope) resolved.searchParams.set("scope", scope);
|
|
190
|
+
if (scope === "all") {
|
|
191
|
+
resolved.searchParams.delete("limit");
|
|
192
|
+
resolved.searchParams.delete("offset");
|
|
193
|
+
resolved.searchParams.delete("page");
|
|
194
|
+
}
|
|
195
|
+
if (isAbsoluteUrl(formatted)) return resolved.toString();
|
|
196
|
+
return `${resolved.pathname}${resolved.search}${resolved.hash}`;
|
|
197
|
+
}
|
|
198
|
+
function downloadFilename(url, format, label) {
|
|
199
|
+
return `${sanitizeFilenameStem(label || filenameStemFromUrl(url) || "clicky-output")}${downloadExtension(format)}`;
|
|
200
|
+
}
|
|
201
|
+
function filenameStemFromUrl(url) {
|
|
202
|
+
const base = typeof window === "undefined" ? "http://localhost" : window.location.origin;
|
|
203
|
+
return new URL(url, base).pathname.split("/").filter(Boolean).at(-1) ?? "";
|
|
204
|
+
}
|
|
205
|
+
function sanitizeFilenameStem(value) {
|
|
206
|
+
return stripSurroundingChars(value.trim().replace(/\.[a-z0-9]+$/i, "").replace(/[^a-z0-9._-]+/gi, "-").replace(/-+/g, "-"), "._-") || "clicky-output";
|
|
207
|
+
}
|
|
208
|
+
function downloadExtension(format) {
|
|
209
|
+
switch (format) {
|
|
210
|
+
case "clicky": return ".clicky.json";
|
|
211
|
+
case "json": return ".json";
|
|
212
|
+
case "ndjson": return ".ndjson";
|
|
213
|
+
case "pdf": return ".pdf";
|
|
214
|
+
case "html": return ".html";
|
|
215
|
+
case "markdown": return ".md";
|
|
216
|
+
case "yaml": return ".yaml";
|
|
217
|
+
case "csv": return ".csv";
|
|
218
|
+
case "pretty": return ".txt";
|
|
219
|
+
case "excel": return ".xlsx";
|
|
220
|
+
case "slack": return ".slack.json";
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
var CLICKY_DOWNLOAD_FRAME_ID = "clicky-download-frame";
|
|
224
|
+
function triggerDownload(url) {
|
|
225
|
+
if (typeof document === "undefined") return;
|
|
226
|
+
let frame = document.getElementById(CLICKY_DOWNLOAD_FRAME_ID);
|
|
227
|
+
if (!frame) {
|
|
228
|
+
frame = document.createElement("iframe");
|
|
229
|
+
frame.id = CLICKY_DOWNLOAD_FRAME_ID;
|
|
230
|
+
frame.name = CLICKY_DOWNLOAD_FRAME_ID;
|
|
231
|
+
frame.title = "Clicky download target";
|
|
232
|
+
frame.setAttribute("aria-hidden", "true");
|
|
233
|
+
frame.tabIndex = -1;
|
|
234
|
+
frame.style.display = "none";
|
|
235
|
+
document.body.appendChild(frame);
|
|
236
|
+
}
|
|
237
|
+
frame.src = url;
|
|
238
|
+
}
|
|
239
|
+
function isAbsoluteUrl(url) {
|
|
240
|
+
return /^[a-z][a-z\d+\-.]*:/i.test(url) || url.startsWith("//");
|
|
241
|
+
}
|
|
242
|
+
export { CLICKY_OVERFLOW_VIEW_FORMATS, CLICKY_PRIMARY_VIEW_FORMATS, buildFormatUrl, exportFormatOptions, exportScopeOptions, getDownloadFormats, getRemoteFormatMeta, runClickyDownload };
|
|
243
|
+
|
|
244
|
+
//# sourceMappingURL=clicky-download.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clicky-download.js","names":[],"sources":["../../src/data/clicky-download.ts"],"sourcesContent":["import type { StaticIconComponent } from \"./Icon\";\nimport {\n UiComment,\n UiEye,\n UiFileCode,\n UiFileSpreadsheet,\n UiFileText,\n UiJson,\n UiMarkdown,\n UiTable,\n} from \"../icons\";\nimport type { DataTablePagination } from \"./DataTable\";\nimport type {\n ClickyExportFormatOption,\n ClickyExportScopeOption,\n} from \"./clicky-export/ClickyExportDialog\";\nimport { stripSurroundingChars } from \"../lib/string\";\n\n// Remote format and download helpers shared by the Clicky viewer and\n// useClickyTableMenu. Kept out of Clicky.tsx so the hook can be exported without\n// breaking fast refresh (react/only-export-components).\n\nexport const CLICKY_PRIMARY_VIEW_FORMATS = [\"clicky\", \"json\"] as const;\nexport const CLICKY_OVERFLOW_VIEW_FORMATS = [\n \"pdf\",\n \"html\",\n \"markdown\",\n \"yaml\",\n \"csv\",\n \"pretty\",\n \"excel\",\n \"slack\",\n] as const;\nconst CLICKY_DOWNLOAD_FORMATS = [\n \"yaml\",\n \"json\",\n \"csv\",\n \"pdf\",\n \"markdown\",\n] as const;\n\nexport type ClickyPrimaryViewFormat =\n (typeof CLICKY_PRIMARY_VIEW_FORMATS)[number];\nexport type ClickyOverflowViewFormat =\n (typeof CLICKY_OVERFLOW_VIEW_FORMATS)[number];\nexport type ClickyRemoteFormat =\n | ClickyPrimaryViewFormat\n | ClickyOverflowViewFormat\n | \"ndjson\";\n\nexport type ClickyDownloadOptions = {\n /** Show all advertised download formats in the overflow menu. */\n all?: boolean;\n /** Override the download menu label. */\n label?: string;\n /** Formats advertised by the endpoint. Defaults to Clicky's legacy set. */\n formats?: ClickyRemoteFormat[];\n /** Result ranges advertised by the endpoint. Legacy endpoints omit this. */\n scopes?: ClickyDownloadScope[];\n /** Whether all-row export streams or uses a compatibility buffer. */\n allRowsMode?: \"streaming\" | \"buffered\";\n /** Per-format server row caps, for example `{ pdf: 1000 }`. */\n formatMaxRows?: Partial<Record<ClickyRemoteFormat, number>>;\n /** Prepare an export-specific resource before the native download starts. */\n prepare?: (request: {\n format: ClickyRemoteFormat;\n scope?: ClickyDownloadScope;\n }) => Promise<{ url: string; label?: string }>;\n};\n\nexport type ClickyDownloadScope = \"page\" | \"all\";\n\nexport function getDownloadFormats({\n url,\n download,\n}: {\n url?: string | undefined;\n download?: ClickyDownloadOptions | undefined;\n}): ClickyRemoteFormat[] {\n const enabled = download ? (download.all ?? true) : !!url;\n\n if (!enabled || !url) {\n return [];\n }\n\n return download?.formats?.length\n ? [...download.formats]\n : [...CLICKY_DOWNLOAD_FORMATS];\n}\n\nfunction getDownloadScopes(\n download?: ClickyDownloadOptions,\n): Array<ClickyDownloadScope | undefined> {\n return download?.scopes?.length ? download.scopes : [undefined];\n}\n\nfunction downloadDescription(\n format: ClickyRemoteFormat,\n scope: ClickyDownloadScope | undefined,\n download?: ClickyDownloadOptions,\n) {\n const maxRows = download?.formatMaxRows?.[format];\n if (maxRows) {\n return `Limited to ${maxRows.toLocaleString()} rows`;\n }\n if (scope === \"all\" && download?.allRowsMode === \"buffered\") {\n return \"Compatibility mode buffers the complete result\";\n }\n if (scope === \"all\" && download?.allRowsMode === \"streaming\") {\n return \"Streams rows as they are read\";\n }\n return undefined;\n}\n\n// exportFormatOptions describes the advertised formats for the export dialog.\n// The dialog is presentational and knows nothing about Clicky's formats, so the\n// labels, icons and blurbs are resolved here, from the same metadata the view\n// switcher reads.\nexport function exportFormatOptions({\n url,\n download,\n}: {\n url?: string | undefined;\n download?: ClickyDownloadOptions | undefined;\n}): ClickyExportFormatOption[] {\n if (!url) return [];\n return getDownloadFormats({ url, download }).map((format) => {\n const meta = getRemoteFormatMeta(format);\n return {\n format,\n label: meta.label,\n description: meta.description,\n icon: meta.icon,\n ...(meta.iconClassName ? { iconClassName: meta.iconClassName } : {}),\n };\n });\n}\n\n// exportScopeOptions describes the ranges the endpoint serves. The per-format\n// note is a function rather than a string because a ceiling belongs to a format\n// and a range together: a PDF stops at 1,000 rows, the CSV beside it does not.\n//\n// The all-rows label carries the count the table was told, because \"all\" is the\n// one number a person needs before starting an export and the only one the page\n// in front of them does not show.\nexport function exportScopeOptions(\n download?: ClickyDownloadOptions,\n pagination?: DataTablePagination,\n): ClickyExportScopeOption[] {\n return getDownloadScopes(download).map((scope) => ({\n scope,\n label:\n scope === \"all\" || scope === undefined\n ? allRowsLabel(pagination)\n : \"Current page\",\n note: (format: string) =>\n downloadDescription(format as ClickyRemoteFormat, scope, download),\n }));\n}\n\n// allRowsLabel states the total when the backend gave one. A \"gte\" total is a\n// lower bound a backend stopped counting at, so it is rendered as one rather\n// than as a count nobody promised.\nfunction allRowsLabel(pagination?: DataTablePagination): string {\n const total = pagination?.total;\n if (total === undefined) return \"All rows\";\n const count = total.toLocaleString();\n return pagination?.totalRelation === \"gte\"\n ? `All ${count}+ rows`\n : `All ${count} rows`;\n}\n\n// runClickyDownload is prepareDownload for a caller that awaits it. The export\n// dialog reports its own failure and stays open on one, so the reason has to\n// come back as a rejection rather than through a callback.\nexport async function runClickyDownload({\n url,\n format,\n scope,\n download,\n}: {\n url: string;\n format: ClickyRemoteFormat;\n scope?: ClickyDownloadScope | undefined;\n download?: ClickyDownloadOptions | undefined;\n}): Promise<void> {\n let failure = \"\";\n await prepareDownload({\n url,\n format,\n ...(scope ? { scope } : {}),\n ...(download ? { download } : {}),\n onError: (message) => {\n failure = message;\n },\n });\n if (failure) throw new Error(failure);\n}\n\nasync function prepareDownload({\n url,\n format,\n scope,\n download,\n onPending,\n onError,\n}: {\n url: string;\n format: ClickyRemoteFormat;\n scope?: ClickyDownloadScope | undefined;\n download?: ClickyDownloadOptions | undefined;\n onPending?: ((pending: boolean) => void) | undefined;\n onError?: ((message: string) => void) | undefined;\n}) {\n onError?.(\"\");\n onPending?.(true);\n try {\n const prepared = download?.prepare\n ? await download.prepare({ format, ...(scope ? { scope } : {}) })\n : { url, ...(download?.label ? { label: download.label } : {}) };\n if (!prepared.url.trim()) {\n throw new Error(\"The prepared download URL is empty\");\n }\n triggerDownload(\n buildDownloadUrl(\n prepared.url,\n format,\n prepared.label ?? download?.label,\n scope,\n ),\n );\n } catch (error) {\n onError?.(\n error instanceof Error ? error.message : \"Preparing the download failed\",\n );\n } finally {\n onPending?.(false);\n }\n}\n\nexport function getRemoteFormatMeta(format: ClickyRemoteFormat): {\n label: string;\n description: string;\n icon: StaticIconComponent;\n iconClassName?: string;\n accept: string;\n} {\n switch (format) {\n case \"clicky\":\n return {\n label: \"Clicky\",\n description: \"Rendered Clicky JSON with the rich Clicky viewer\",\n icon: UiEye,\n accept:\n \"application/json+clicky, application/clicky+json;q=0.9, application/json;q=0.8,*/*;q=0.7\",\n };\n case \"json\":\n return {\n label: \"JSON\",\n description: \"Plain JSON for inspecting the raw response body\",\n icon: UiJson,\n accept: \"application/json, text/plain;q=0.8,*/*;q=0.7\",\n };\n case \"ndjson\":\n return {\n label: \"NDJSON\",\n description: \"Newline-delimited JSON for streaming data pipelines\",\n icon: UiJson,\n accept: \"application/x-ndjson, application/ndjson;q=0.9,*/*;q=0.7\",\n };\n case \"pdf\":\n return {\n label: \"PDF\",\n description: \"Portable document for sharing and printing\",\n icon: UiFileText,\n iconClassName: \"text-rose-600 dark:text-rose-400\",\n accept: \"application/pdf, */*;q=0.7\",\n };\n case \"html\":\n return {\n label: \"HTML\",\n description: \"Browser-ready HTML preview of the formatted output\",\n icon: UiFileCode,\n accept: \"text/html, */*;q=0.7\",\n };\n case \"markdown\":\n return {\n label: \"Markdown\",\n description: \"Markdown formatted for docs, comments, and chat\",\n icon: UiMarkdown,\n accept: \"text/markdown, text/plain;q=0.8,*/*;q=0.7\",\n };\n case \"yaml\":\n return {\n label: \"YAML\",\n description: \"YAML for config-friendly inspection and export\",\n icon: UiFileCode,\n iconClassName: \"text-violet-600 dark:text-violet-400\",\n accept: \"application/yaml, text/yaml;q=0.9, text/plain;q=0.8,*/*;q=0.7\",\n };\n case \"csv\":\n return {\n label: \"CSV\",\n description: \"Comma-separated values for spreadsheets and imports\",\n icon: UiFileSpreadsheet,\n iconClassName: \"text-emerald-600 dark:text-emerald-400\",\n accept: \"text/csv, text/plain;q=0.8,*/*;q=0.7\",\n };\n case \"pretty\":\n return {\n label: \"Pretty\",\n description: \"Human-readable plain text output from the formatter\",\n icon: UiFileCode,\n accept: \"text/plain, */*;q=0.7\",\n };\n case \"excel\":\n return {\n label: \"Excel\",\n description: \"Spreadsheet workbook for offline analysis\",\n icon: UiTable,\n accept:\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, */*;q=0.7\",\n };\n case \"slack\":\n return {\n label: \"Slack\",\n description: \"Slack Block Kit JSON for chat-native output\",\n icon: UiComment,\n accept:\n \"application/vnd.slack.block-kit+json, application/json;q=0.8,*/*;q=0.7\",\n };\n }\n}\n\nexport function buildFormatUrl(url: string, format: ClickyRemoteFormat) {\n const base =\n typeof window === \"undefined\" ? \"http://localhost\" : window.location.origin;\n const resolved = new URL(url, base);\n resolved.searchParams.set(\n \"format\",\n format === \"clicky\" ? \"clicky-json\" : format,\n );\n\n if (isAbsoluteUrl(url)) {\n return resolved.toString();\n }\n\n return `${resolved.pathname}${resolved.search}${resolved.hash}`;\n}\n\nfunction buildDownloadUrl(\n url: string,\n format: ClickyRemoteFormat,\n label?: string | undefined,\n scope?: ClickyDownloadScope | undefined,\n) {\n const formatted = buildFormatUrl(url, format);\n const base =\n typeof window === \"undefined\" ? \"http://localhost\" : window.location.origin;\n const resolved = new URL(formatted, base);\n resolved.searchParams.set(\"filename\", downloadFilename(url, format, label));\n resolved.searchParams.set(\"_download\", Date.now().toString());\n if (scope) {\n resolved.searchParams.set(\"scope\", scope);\n }\n if (scope === \"all\") {\n resolved.searchParams.delete(\"limit\");\n resolved.searchParams.delete(\"offset\");\n resolved.searchParams.delete(\"page\");\n }\n\n if (isAbsoluteUrl(formatted)) {\n return resolved.toString();\n }\n\n return `${resolved.pathname}${resolved.search}${resolved.hash}`;\n}\n\nfunction downloadFilename(\n url: string,\n format: ClickyRemoteFormat,\n label?: string | undefined,\n) {\n const stem = sanitizeFilenameStem(\n label || filenameStemFromUrl(url) || \"clicky-output\",\n );\n return `${stem}${downloadExtension(format)}`;\n}\n\nfunction filenameStemFromUrl(url: string) {\n const base =\n typeof window === \"undefined\" ? \"http://localhost\" : window.location.origin;\n const resolved = new URL(url, base);\n const segment = resolved.pathname.split(\"/\").filter(Boolean).at(-1);\n return segment ?? \"\";\n}\n\nfunction sanitizeFilenameStem(value: string) {\n const cleaned = stripSurroundingChars(\n value\n .trim()\n .replace(/\\.[a-z0-9]+$/i, \"\")\n .replace(/[^a-z0-9._-]+/gi, \"-\")\n .replace(/-+/g, \"-\"),\n \"._-\",\n );\n\n return cleaned || \"clicky-output\";\n}\n\nfunction downloadExtension(format: ClickyRemoteFormat) {\n switch (format) {\n case \"clicky\":\n return \".clicky.json\";\n case \"json\":\n return \".json\";\n case \"ndjson\":\n return \".ndjson\";\n case \"pdf\":\n return \".pdf\";\n case \"html\":\n return \".html\";\n case \"markdown\":\n return \".md\";\n case \"yaml\":\n return \".yaml\";\n case \"csv\":\n return \".csv\";\n case \"pretty\":\n return \".txt\";\n case \"excel\":\n return \".xlsx\";\n case \"slack\":\n return \".slack.json\";\n }\n}\n\nconst CLICKY_DOWNLOAD_FRAME_ID = \"clicky-download-frame\";\n\nfunction triggerDownload(url: string) {\n if (typeof document === \"undefined\") return;\n\n let frame = document.getElementById(\n CLICKY_DOWNLOAD_FRAME_ID,\n ) as HTMLIFrameElement | null;\n if (!frame) {\n frame = document.createElement(\"iframe\");\n frame.id = CLICKY_DOWNLOAD_FRAME_ID;\n frame.name = CLICKY_DOWNLOAD_FRAME_ID;\n frame.title = \"Clicky download target\";\n frame.setAttribute(\"aria-hidden\", \"true\");\n frame.tabIndex = -1;\n frame.style.display = \"none\";\n document.body.appendChild(frame);\n }\n\n frame.src = url;\n}\n\nfunction isAbsoluteUrl(url: string) {\n return /^[a-z][a-z\\d+\\-.]*:/i.test(url) || url.startsWith(\"//\");\n}\n"],"mappings":"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"}
|
|
@@ -19,6 +19,8 @@ export type { HttpExchangeRequest, HttpExchangeResponse, GoProfileRecord, HttpEx
|
|
|
19
19
|
export { OtelTraceDetail } from './otel/OtelTraceDetail.js';
|
|
20
20
|
export { OtelTraceSearch } from './otel/OtelTraceSearch.js';
|
|
21
21
|
export { OtelTraceTable, type OtelTraceTableProps } from './otel/OtelTraceTable.js';
|
|
22
|
+
export { profileSpans } from './otel/profile-spans.js';
|
|
23
|
+
export { profileListingSort, profileTraceListing } from './otel/profile-listing.js';
|
|
22
24
|
export { searchParams as otelSearchParams, readSearchValues as readOtelSearchValues } from './otel/api.js';
|
|
23
25
|
export { durationOf as otelDurationOf } from './otel/query.js';
|
|
24
26
|
export type { Span as OtelSpan, Summary as OtelSummary, SearchForm as OtelSearchForm } from './otel/api.js';
|