UNPKG

@flanksource/clicky-ui

Version:

Flanksource Clicky UI — React component library built on shadcn/ui with light/dark and density theming.

1,337 lines (1,336 loc) 77.7 kB
import { jsx, jsxs, Fragment } from "react/jsx-runtime"; import { QueryClient, QueryClientProvider, useQuery } from "@tanstack/react-query"; import { useState, useMemo, useEffect, createContext, useRef, useContext, Fragment as Fragment$1 } from "react"; import { FilterForm } from "../rpc/FilterForm.js"; import { parseJsonBody } from "../rpc/classify.js"; import { packParameterValues, pruneParameterValues } from "../rpc/formMetadata.js"; import { isPositionalParam } from "../rpc/types.js"; import { useOperations } from "../rpc/useOperations.js"; import { cn } from "../lib/utils.js"; import { DataTable } from "./DataTable.js"; import { Tree } from "./Tree.js"; import { Icon } from "./Icon.js"; import { JsonView } from "./JsonView.js"; import { highlightCode } from "./code-highlight.js"; import { StackTrace } from "./diagnostics/RenderedStackTrace.js"; import { Badge } from "./Badge.js"; import { HoverCard } from "../overlay/HoverCard.js"; import { Modal } from "../overlay/Modal.js"; import { TagList, normalizeTags, TagActionsProvider } from "./cells/TagList.js"; const CLICKY_PRIMARY_VIEW_FORMATS = ["clicky", "json"]; const CLICKY_OVERFLOW_VIEW_FORMATS = [ "pdf", "html", "markdown", "yaml", "csv", "pretty", "excel", "slack" ]; const CLICKY_DOWNLOAD_FORMATS = ["json", "clicky", ...CLICKY_OVERFLOW_VIEW_FORMATS]; const clickyRuntimeContextDefault = { operations: [], operationsLoading: false }; const ClickyRuntimeContext = createContext(clickyRuntimeContextDefault); function Clicky(props) { const [queryClient] = useState( () => new QueryClient({ defaultOptions: { queries: { staleTime: 3e4, retry: 1 } } }) ); const content = /* @__PURE__ */ jsx( ClickyRuntimeProvider, { ...props.commandRuntime ? { commandRuntime: props.commandRuntime } : {}, ...props.onTableRowClick ? { onTableRowClick: props.onTableRowClick } : {}, ...props.getTableRowHref ? { getTableRowHref: props.getTableRowHref } : {}, ...props.isTableRowClickable ? { isTableRowClickable: props.isTableRowClickable } : {}, children: props.url ? /* @__PURE__ */ jsx(ClickyRemoteRenderer, { ...props, url: props.url }) : /* @__PURE__ */ jsx( ClickyContent, { data: props.data, ...props.className ? { className: props.className } : {} } ) } ); if (props.url || props.commandRuntime) { return /* @__PURE__ */ jsx(QueryClientProvider, { client: queryClient, children: content }); } return content; } function ClickyRuntimeProvider({ commandRuntime, onTableRowClick, getTableRowHref, isTableRowClickable, children }) { if (!commandRuntime) { return /* @__PURE__ */ jsx( ClickyRuntimeContext.Provider, { value: onTableRowClick || getTableRowHref || isTableRowClickable ? { ...clickyRuntimeContextDefault, onTableRowClick, getTableRowHref, isTableRowClickable } : clickyRuntimeContextDefault, children } ); } return /* @__PURE__ */ jsx( ClickyCommandRuntimeProvider, { commandRuntime, ...onTableRowClick ? { onTableRowClick } : {}, ...getTableRowHref ? { getTableRowHref } : {}, ...isTableRowClickable ? { isTableRowClickable } : {}, children } ); } function ClickyCommandRuntimeProvider({ commandRuntime, onTableRowClick, getTableRowHref, isTableRowClickable, children }) { const { operations, isLoading } = useOperations(commandRuntime.client); const value = useMemo( () => ({ commandRuntime, onTableRowClick, getTableRowHref, isTableRowClickable, operations, operationsLoading: isLoading }), [commandRuntime, getTableRowHref, isLoading, isTableRowClickable, onTableRowClick, operations] ); return /* @__PURE__ */ jsx(ClickyRuntimeContext.Provider, { value, children }); } function ClickyContent({ data, className }) { if (data === void 0) { return /* @__PURE__ */ jsx( ClickyNotice, { className, title: "No Clicky data", message: "Provide either a local data payload or a URL." } ); } const parsed = parseClickyData(data); if (!parsed.ok) { return /* @__PURE__ */ jsx(ClickyInvalidPayload, { parsed, className }); } return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx(ClickyNodeRenderer, { node: parsed.document.node }) }); } function ClickyRemoteRenderer({ data, url, view, download, className }) { var _a, _b; const availableViews = useMemo(() => getAvailableViews({ data, url, view }), [data, url, view]); const primaryViews = useMemo(() => availableViews.filter(isPrimaryViewFormat), [availableViews]); const overflowViews = useMemo( () => availableViews.filter(isOverflowViewFormat), [availableViews] ); const downloadFormats = useMemo(() => getDownloadFormats({ url, download }), [download, url]); const [activeView, setActiveView] = useState( () => availableViews[0] ?? "clicky" ); useEffect(() => { if (!availableViews.includes(activeView)) { setActiveView(availableViews[0] ?? "clicky"); } }, [activeView, availableViews]); const formattedUrl = buildFormatUrl(url, activeView); const activeQuery = useQuery({ queryKey: ["clicky", activeView, formattedUrl], enabled: shouldFetchRemoteView(activeView), queryFn: async () => fetchRemoteFormat(formattedUrl, activeView) }); const effectiveClickyData = activeView === "clicky" && ((_a = activeQuery.data) == null ? void 0 : _a.kind) === "text" ? activeQuery.data.text : data; const fallbackJsonData = useMemo(() => parseJsonValue(data), [data]); const effectiveJsonData = activeView === "json" && ((_b = activeQuery.data) == null ? void 0 : _b.kind) === "text" ? parseJsonValue(activeQuery.data.text) : fallbackJsonData; const activeOverflowView = isOverflowViewFormat(activeView) ? activeView : null; const canDownload = downloadFormats.length > 0; const loadingMessage = `Fetching ${formattedUrl}`; return /* @__PURE__ */ jsxs("div", { className: cn("space-y-density-3", className), children: [ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 rounded-md border border-border bg-muted/20 px-density-3 py-density-2", children: [ primaryViews.length > 1 && /* @__PURE__ */ jsx("div", { role: "radiogroup", "aria-label": "Clicky view mode", className: "flex flex-wrap gap-1", children: primaryViews.map((mode) => /* @__PURE__ */ jsx( "button", { type: "button", role: "radio", "aria-checked": activeView === mode, onClick: () => setActiveView(mode), className: cn( "rounded-md border px-2.5 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", activeView === mode ? "border-foreground/20 bg-accent text-foreground" : "border-transparent text-muted-foreground hover:border-foreground/20 hover:text-foreground" ), children: formatViewLabel(mode) }, mode )) }), overflowViews.length > 0 && /* @__PURE__ */ jsx( ClickyViewMenu, { activeFormat: activeOverflowView, formats: overflowViews, onSelect: setActiveView } ), /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-2", children: [ shouldFetchRemoteView(activeView) && activeQuery.isFetching && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Refreshing…" }), canDownload && /* @__PURE__ */ jsx(ClickyDownloadMenu, { url, formats: downloadFormats, label: download == null ? void 0 : download.label }) ] }) ] }), activeView === "pdf" ? /* @__PURE__ */ jsx(ClickyPdfPreview, { src: formattedUrl }) : activeView === "html" ? /* @__PURE__ */ jsx(ClickyHtmlPreview, { src: formattedUrl }) : activeView === "excel" ? /* @__PURE__ */ jsx( ClickyUnsupportedPreview, { title: "Excel preview", message: "Excel output is available for download, but not inline preview.", href: formattedUrl } ) : activeView === "clicky" ? activeQuery.isPending && effectiveClickyData === void 0 ? /* @__PURE__ */ jsx(ClickyNotice, { title: "Loading Clicky", message: loadingMessage }) : activeQuery.isError && effectiveClickyData === void 0 ? /* @__PURE__ */ jsx( ClickyNotice, { title: "Clicky request failed", message: activeQuery.error instanceof Error ? activeQuery.error.message : "Request failed", tone: "destructive" } ) : /* @__PURE__ */ jsxs(Fragment, { children: [ activeQuery.isError && data !== void 0 && /* @__PURE__ */ jsx( ClickyNotice, { title: "Remote refresh failed", message: "Showing the local fallback payload instead.", tone: "warning" } ), /* @__PURE__ */ jsx(ClickyContent, { data: effectiveClickyData }) ] }) : activeView === "json" ? activeQuery.isPending && effectiveJsonData === void 0 ? /* @__PURE__ */ jsx(ClickyNotice, { title: "Loading JSON", message: loadingMessage }) : activeQuery.isError && effectiveJsonData === void 0 ? /* @__PURE__ */ jsx( ClickyNotice, { title: "JSON request failed", message: activeQuery.error instanceof Error ? activeQuery.error.message : "Request failed", tone: "destructive" } ) : /* @__PURE__ */ jsxs(Fragment, { children: [ activeQuery.isError && fallbackJsonData !== void 0 && /* @__PURE__ */ jsx( ClickyNotice, { title: "Remote refresh failed", message: "Showing the local fallback payload instead.", tone: "warning" } ), /* @__PURE__ */ jsx(ClickyJsonTree, { value: effectiveJsonData }) ] }) : activeQuery.isPending ? /* @__PURE__ */ jsx(ClickyNotice, { title: `Loading ${formatViewLabel(activeView)}`, message: loadingMessage }) : activeQuery.isError ? /* @__PURE__ */ jsx( ClickyNotice, { title: `${formatViewLabel(activeView)} request failed`, message: activeQuery.error instanceof Error ? activeQuery.error.message : "Request failed", tone: "destructive" } ) : /* @__PURE__ */ jsx(ClickyRemotePreview, { format: activeView, response: activeQuery.data }) ] }); } function ClickyViewMenu({ activeFormat, formats, onSelect }) { const rootRef = useRef(null); const triggerRef = useRef(null); const [open, setOpen] = useState(false); useDismissablePopup(open, rootRef, triggerRef, () => setOpen(false)); return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: "relative", children: [ /* @__PURE__ */ jsx( "button", { ref: triggerRef, type: "button", "aria-label": "Open additional view menu", "aria-haspopup": "menu", "aria-expanded": open, "aria-pressed": activeFormat != null, className: cn( "inline-flex h-[34px] w-[34px] items-center justify-center rounded-md border border-input bg-background text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", activeFormat && "border-foreground/20 bg-accent text-foreground" ), onClick: () => setOpen((current) => !current), children: /* @__PURE__ */ jsx(Icon, { name: "codicon:ellipsis", className: "text-sm" }) } ), open && /* @__PURE__ */ jsx( "div", { role: "menu", className: "absolute left-0 top-[calc(100%+0.375rem)] z-50 min-w-[18rem] rounded-md border border-border bg-popover p-1.5 text-popover-foreground shadow-lg shadow-black/5", children: formats.map((format) => { const active = format === activeFormat; const meta = getRemoteFormatMeta(format); return /* @__PURE__ */ jsxs( "button", { type: "button", role: "menuitemradio", "aria-checked": active, className: cn( "flex w-full items-start gap-3 rounded-md px-3 py-2 text-left text-sm transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:outline-none", active && "bg-accent/70" ), onClick: () => { onSelect(format); setOpen(false); }, children: [ /* @__PURE__ */ jsx( Icon, { name: meta.icon, className: cn( "mt-0.5 shrink-0 text-base text-muted-foreground", active && "text-foreground" ) } ), /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [ /* @__PURE__ */ jsxs("span", { className: "flex items-center justify-between gap-3", children: [ /* @__PURE__ */ jsx("span", { className: "font-medium", children: meta.label }), active && /* @__PURE__ */ jsx(Icon, { name: "codicon:check", className: "text-xs text-muted-foreground" }) ] }), /* @__PURE__ */ jsx("span", { className: "mt-0.5 block text-xs text-muted-foreground", children: meta.description }) ] }) ] }, format ); }) } ) ] }); } function ClickyDownloadMenu({ url, formats, label }) { const rootRef = useRef(null); const triggerRef = useRef(null); const [open, setOpen] = useState(false); const primaryFormat = "json"; const primaryMeta = getRemoteFormatMeta(primaryFormat); useDismissablePopup(open, rootRef, triggerRef, () => setOpen(false)); return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: "relative", children: [ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [ /* @__PURE__ */ jsxs( "button", { type: "button", "aria-label": label ? `Download ${primaryMeta.label} ${label}` : `Download ${primaryMeta.label}`, className: "inline-flex items-center gap-2 rounded-md border border-input bg-background px-3 py-1.5 text-xs font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", onClick: () => submitDownloadRequest(buildFormatUrl(url, primaryFormat)), children: [ /* @__PURE__ */ jsx(Icon, { name: "codicon:cloud-download", className: "text-sm" }), /* @__PURE__ */ jsx("span", { children: `Download ${primaryMeta.label}` }) ] } ), /* @__PURE__ */ jsx( "button", { ref: triggerRef, type: "button", "aria-label": "Open download menu", "aria-haspopup": "menu", "aria-expanded": open, className: "inline-flex h-[34px] w-[34px] items-center justify-center rounded-md border border-input bg-background text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", onClick: () => setOpen((current) => !current), children: /* @__PURE__ */ jsx(Icon, { name: open ? "codicon:chevron-up" : "codicon:chevron-down", className: "text-sm" }) } ) ] }), open && /* @__PURE__ */ jsx( "div", { role: "menu", className: "absolute right-0 top-[calc(100%+0.375rem)] z-50 min-w-[18rem] rounded-md border border-border bg-popover p-1.5 text-popover-foreground shadow-lg shadow-black/5", children: formats.map((format) => { const meta = getRemoteFormatMeta(format); return /* @__PURE__ */ jsxs( "button", { type: "button", role: "menuitem", className: cn( "flex w-full items-start gap-3 rounded-md px-3 py-2 text-left text-sm transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:outline-none" ), onClick: () => { submitDownloadRequest(buildFormatUrl(url, format)); setOpen(false); }, children: [ /* @__PURE__ */ jsx( Icon, { name: meta.icon, className: "mt-0.5 shrink-0 text-base text-muted-foreground" } ), /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [ /* @__PURE__ */ jsx("span", { className: "font-medium", children: meta.label }), /* @__PURE__ */ jsx("span", { className: "mt-0.5 block text-xs text-muted-foreground", children: meta.description }) ] }) ] }, format ); }) } ) ] }); } function useDismissablePopup(open, rootRef, triggerRef, onClose) { useEffect(() => { if (!open) return; const onPointerDown = (event) => { var _a; if (!((_a = rootRef.current) == null ? void 0 : _a.contains(event.target))) { onClose(); } }; const onKeyDown = (event) => { var _a; if (event.key === "Escape") { onClose(); (_a = triggerRef.current) == null ? void 0 : _a.focus(); } }; document.addEventListener("mousedown", onPointerDown); document.addEventListener("keydown", onKeyDown); return () => { document.removeEventListener("mousedown", onPointerDown); document.removeEventListener("keydown", onKeyDown); }; }, [onClose, open, rootRef, triggerRef]); } function parseClickyData(data) { if (typeof data === "string") { try { return normalizeClickyDocument(JSON.parse(data)); } catch (error) { return { ok: false, message: error instanceof Error ? error.message : "Failed to parse JSON", raw: data }; } } return normalizeClickyDocument(data); } function fetchRemoteFormat(url, format) { return fetch(url, { headers: { Accept: getRemoteFormatMeta(format).accept } }).then(async (response) => { if (!response.ok) { throw new Error(`Request failed with ${response.status} ${response.statusText}`.trim()); } const contentType = response.headers.get("Content-Type") ?? ""; if (format === "excel") { return { kind: "blob", blob: await response.blob(), contentType }; } return { kind: "text", text: await response.text(), contentType }; }); } function ClickyInvalidPayload({ parsed, className }) { return /* @__PURE__ */ jsxs( "div", { className: cn( "rounded-md border border-destructive/30 bg-destructive/5 p-density-3", className ), children: [ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-destructive", children: "Invalid Clicky payload" }), /* @__PURE__ */ jsxs("pre", { className: "mt-2 whitespace-pre-wrap break-all text-xs text-muted-foreground", children: [ parsed.message, parsed.raw ? ` ${parsed.raw}` : "" ] }) ] } ); } function ClickyNotice({ title, message, tone = "default", className }) { return /* @__PURE__ */ jsxs( "div", { className: cn( "rounded-md border p-density-3", tone === "destructive" && "border-destructive/30 bg-destructive/5", tone === "warning" && "border-yellow-500/30 bg-yellow-500/5", tone === "default" && "border-border bg-muted/30", className ), children: [ /* @__PURE__ */ jsx( "div", { className: cn( "text-sm font-medium", tone === "destructive" && "text-destructive", tone === "warning" && "text-yellow-700 dark:text-yellow-400" ), children: title } ), /* @__PURE__ */ jsx("div", { className: "mt-1 text-xs text-muted-foreground", children: message }) ] } ); } function ClickyPdfPreview({ src }) { return /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-md border border-border bg-background", children: [ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-border px-3 py-2 text-xs text-muted-foreground", children: [ /* @__PURE__ */ jsx("span", { children: "PDF preview" }), /* @__PURE__ */ jsx("a", { href: src, target: "_blank", rel: "noreferrer", className: "text-primary hover:underline", children: "Open in new tab" }) ] }), /* @__PURE__ */ jsx("iframe", { title: "Clicky PDF preview", src, className: "h-[720px] w-full bg-white" }) ] }); } function ClickyHtmlPreview({ src }) { return /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-md border border-border bg-background", children: [ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-border px-3 py-2 text-xs text-muted-foreground", children: [ /* @__PURE__ */ jsx("span", { children: "HTML preview" }), /* @__PURE__ */ jsx("a", { href: src, target: "_blank", rel: "noreferrer", className: "text-primary hover:underline", children: "Open in new tab" }) ] }), /* @__PURE__ */ jsx( "iframe", { title: "Clicky HTML preview", src, className: "h-[720px] w-full bg-white", sandbox: "allow-same-origin" } ) ] }); } function ClickyUnsupportedPreview({ title, message, href }) { return /* @__PURE__ */ jsx( ClickyNotice, { title, message: /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsxs("span", { children: [ message, " " ] }), /* @__PURE__ */ jsx("a", { href, target: "_blank", rel: "noreferrer", className: "text-primary hover:underline", children: "Open format" }) ] }) } ); } function ClickyRemotePreview({ format, response }) { const meta = getRemoteFormatMeta(format); if (format === "slack") { const parsed = (response == null ? void 0 : response.kind) === "text" ? parseJsonValue(response.text) : void 0; return /* @__PURE__ */ jsx(ClickyJsonTree, { value: parsed, emptyLabel: meta.label }); } return /* @__PURE__ */ jsx( ClickyTextPreview, { title: `${meta.label} output`, content: (response == null ? void 0 : response.kind) === "text" ? response.text : "" } ); } function ClickyTextPreview({ title, content }) { return /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-md border border-border bg-background", children: [ /* @__PURE__ */ jsx("div", { className: "border-b border-border px-3 py-2 text-xs text-muted-foreground", children: title }), /* @__PURE__ */ jsx( "pre", { "aria-label": "Clicky text preview", className: "overflow-auto whitespace-pre-wrap break-words px-3 py-3 font-mono text-xs text-foreground", children: content || "No content" } ) ] }); } function ClickyJsonTree({ value, emptyLabel = "JSON" }) { const roots = useMemo(() => buildJsonTree(value), [value]); if (value === void 0) { return /* @__PURE__ */ jsx( ClickyNotice, { title: `No ${emptyLabel} payload`, message: "The response did not include a JSON body to inspect." } ); } return /* @__PURE__ */ jsx( "div", { "aria-label": "JSON tree", className: "overflow-hidden rounded-md border border-border bg-background", children: /* @__PURE__ */ jsx( Tree, { roots, className: "min-h-[12rem]", showControls: roots.some((node) => { var _a; return (((_a = node.children) == null ? void 0 : _a.length) ?? 0) > 0; }), getKey: (node) => node.id, getChildren: (node) => node.children, renderRow: ({ node }) => /* @__PURE__ */ jsx(ClickyJsonTreeRow, { node }), rowClass: () => "hover:bg-accent", empty: /* @__PURE__ */ jsx("div", { className: "px-3 py-4 text-sm text-muted-foreground", children: "No JSON fields." }), toolbarClassName: "pl-3" } ) } ); } function ClickyJsonTreeRow({ node }) { const primitiveClass = typeof node.value === "string" ? "text-emerald-700 dark:text-emerald-400" : typeof node.value === "number" ? "text-sky-700 dark:text-sky-400" : typeof node.value === "boolean" || node.value === null ? "text-violet-700 dark:text-violet-400" : "text-muted-foreground"; return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-start gap-2 font-mono text-xs", children: [ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-foreground", children: node.key }), /* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground", children: ":" }), /* @__PURE__ */ jsx("span", { className: cn("min-w-0 break-words", primitiveClass), children: node.preview }) ] }); } function getAvailableViews({ data, url, view }) { const allowClicky = resolveViewConfigFlag(view, "clicky", data, url); const allowJson = resolveViewConfigFlag(view, "json", data, url); const next = []; if (allowClicky && (url || data !== void 0)) { next.push("clicky"); } if (allowJson && (url || data !== void 0)) { next.push("json"); } for (const format of CLICKY_OVERFLOW_VIEW_FORMATS) { if (resolveViewConfigFlag(view, format, data, url) && url) { next.push(format); } } if (next.length === 0 && (url || data !== void 0)) { next.push("clicky"); } return next; } function getDownloadFormats({ url, download }) { const enabled = download ? download.all ?? true : !!url; if (!enabled || !url) { return []; } return [...CLICKY_DOWNLOAD_FORMATS]; } function getRemoteFormatMeta(format) { switch (format) { case "clicky": return { label: "Clicky", description: "Rendered Clicky JSON with the rich Clicky viewer", icon: "codicon:preview", accept: "application/json+clicky, application/clicky+json;q=0.9, application/json;q=0.8,*/*;q=0.7" }; case "json": return { label: "JSON", description: "Plain JSON for inspecting the raw response body", icon: "vscode-icons:file-type-json", accept: "application/json, text/plain;q=0.8,*/*;q=0.7" }; case "pdf": return { label: "PDF", description: "Portable document for sharing and printing", icon: "vscode-icons:file-type-pdf2", accept: "application/pdf, */*;q=0.7" }; case "html": return { label: "HTML", description: "Browser-ready HTML preview of the formatted output", icon: "vscode-icons:file-type-html", accept: "text/html, */*;q=0.7" }; case "markdown": return { label: "Markdown", description: "Markdown formatted for docs, comments, and chat", icon: "vscode-icons:file-type-markdown", accept: "text/markdown, text/plain;q=0.8,*/*;q=0.7" }; case "yaml": return { label: "YAML", description: "YAML for config-friendly inspection and export", icon: "vscode-icons:file-type-yaml", accept: "application/yaml, text/yaml;q=0.9, text/plain;q=0.8,*/*;q=0.7" }; case "csv": return { label: "CSV", description: "Comma-separated values for spreadsheets and imports", icon: "codicon:table", accept: "text/csv, text/plain;q=0.8,*/*;q=0.7" }; case "pretty": return { label: "Pretty", description: "Human-readable plain text output from the formatter", icon: "codicon:file-code", accept: "text/plain, */*;q=0.7" }; case "excel": return { label: "Excel", description: "Spreadsheet workbook for offline analysis", icon: "codicon:table", accept: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, */*;q=0.7" }; case "slack": return { label: "Slack", description: "Slack Block Kit JSON for chat-native output", icon: "codicon:comment-discussion", accept: "application/vnd.slack.block-kit+json, application/json;q=0.8,*/*;q=0.7" }; } } function formatViewLabel(mode) { return getRemoteFormatMeta(mode).label; } function buildFormatUrl(url, format) { const base = typeof window === "undefined" ? "http://localhost" : window.location.origin; const resolved = new URL(url, base); resolved.searchParams.set("format", format === "clicky" ? "clicky-json" : format); if (isAbsoluteUrl(url)) { return resolved.toString(); } return `${resolved.pathname}${resolved.search}${resolved.hash}`; } function shouldFetchRemoteView(format) { return format !== "pdf" && format !== "html" && format !== "excel"; } function isPrimaryViewFormat(format) { return CLICKY_PRIMARY_VIEW_FORMATS.includes(format); } function isOverflowViewFormat(format) { return CLICKY_OVERFLOW_VIEW_FORMATS.includes(format); } function resolveViewConfigFlag(view, format, data, url) { const defaultEnabled = format === "clicky" || format === "json" ? url != null || data !== void 0 : !!url; if (view === void 0) { return defaultEnabled; } if (Array.isArray(view)) { if (view.length === 0) { return false; } if (format === "clicky") { return view.includes("clicky") || view.includes("json"); } return view.includes(format); } if (format in view) { return Boolean(view[format]); } if (format === "clicky" && view.json === true) { return true; } return defaultEnabled; } function parseJsonValue(data) { if (data === void 0) { return void 0; } if (typeof data !== "string") { return data; } const trimmed = data.trim(); if (!trimmed) { return ""; } try { return JSON.parse(trimmed); } catch { return data; } } function buildJsonTree(value) { if (Array.isArray(value)) { return value.map((entry, index) => buildJsonTreeNode(String(index), entry, `$[${index}]`)); } if (isPlainObject(value)) { return Object.entries(value).map(([key, entry]) => buildJsonTreeNode(key, entry, `$.${key}`)); } if (value === void 0) { return []; } return [buildJsonTreeNode("$", value, "$")]; } function buildJsonTreeNode(key, value, id) { const children = getJsonChildren(value, id); return { id, key, value, preview: summarizeJsonValue(value), ...children ? { children } : {} }; } function getJsonChildren(value, path) { if (Array.isArray(value)) { return value.map( (entry, index) => buildJsonTreeNode(String(index), entry, `${path}[${index}]`) ); } if (isPlainObject(value)) { return Object.entries(value).map( ([key, entry]) => buildJsonTreeNode(key, entry, `${path}.${key}`) ); } return void 0; } function summarizeJsonValue(value) { if (Array.isArray(value)) { return `[${value.length} item${value.length === 1 ? "" : "s"}]`; } if (isPlainObject(value)) { const count = Object.keys(value).length; return `{${count} key${count === 1 ? "" : "s"}}`; } if (typeof value === "string") { return JSON.stringify(value); } if (value === void 0) { return "undefined"; } return String(value); } function isPlainObject(value) { return value != null && typeof value === "object" && !Array.isArray(value); } function submitDownloadRequest(url) { if (typeof document === "undefined") return; const form = document.createElement("form"); form.method = "GET"; form.action = url; form.style.display = "none"; document.body.appendChild(form); form.submit(); document.body.removeChild(form); } function isAbsoluteUrl(url) { return /^[a-z][a-z\d+\-.]*:/i.test(url) || url.startsWith("//"); } function normalizeClickyDocument(data) { if (!data || typeof data !== "object") { return { ok: false, message: "Payload must be an object", raw: String(data ?? "") }; } const candidate = data; if ("version" in candidate && candidate.version === 1 && candidate.node && isClickyNode(candidate.node)) { return { ok: true, document: candidate }; } if (isClickyNode(candidate)) { return { ok: true, document: { version: 1, node: candidate } }; } return { ok: false, message: "Payload is neither a Clicky document nor a Clicky node", raw: JSON.stringify(data, null, 2) }; } function isClickyNode(value) { return !!value && typeof value === "object" && typeof value.kind === "string"; } function ClickyNodeRenderer({ node }) { if (!node) return null; switch (node.kind) { case "text": return /* @__PURE__ */ jsx(ClickyText, { node }); case "link": return /* @__PURE__ */ jsx(ClickyLinkNode, { node }); case "link-command": return /* @__PURE__ */ jsx(ClickyLinkCommandNode, { node }); case "icon": return /* @__PURE__ */ jsx(ClickyIconNode, { node }); case "list": return /* @__PURE__ */ jsx(ClickyList, { node }); case "map": return /* @__PURE__ */ jsx(ClickyMap, { node }); case "table": return /* @__PURE__ */ jsx(ClickyTable, { node }); case "tree": return /* @__PURE__ */ jsx(ClickyTreeNode, { node }); case "code": return /* @__PURE__ */ jsx(ClickyCodeBlock, { node }); case "collapsed": return /* @__PURE__ */ jsx(ClickyCollapsed, { node }); case "stacktrace": return /* @__PURE__ */ jsx(ClickyStackTraceNode, { node }); case "button": return /* @__PURE__ */ jsx(ClickyButtonNode, { node }); case "button-group": return /* @__PURE__ */ jsx(ClickyButtonGroup, { node }); case "html": return /* @__PURE__ */ jsx(ClickyHtmlNode, { node }); case "comment": return /* @__PURE__ */ jsx(ClickyComment, { node }); case "badge": return /* @__PURE__ */ jsx(ClickyBadgeNode, { node }); default: return /* @__PURE__ */ jsx("pre", { className: "rounded-md border border-border bg-muted p-density-3 text-xs", children: JSON.stringify(node, null, 2) }); } } function ClickyComment({ node }) { var _a, _b, _c, _d, _e; const text = node.text ?? node.plain; const content = /* @__PURE__ */ jsx(ClickyInlineContent, { node }); const maxWidth = ((_a = node.style) == null ? void 0 : _a.maxWidth) && node.style.maxWidth > 0 ? Math.min(node.style.maxWidth, 50) : 50; const maxLines = ((_b = node.style) == null ? void 0 : _b.maxLines) && node.style.maxLines > 0 ? Math.min(node.style.maxLines, 3) : 3; const inlineStyle = toInlineStyle( { ...node.style, maxWidth, maxLines }, text ); if (!text && !((_c = node.children) == null ? void 0 : _c.length)) return null; return /* @__PURE__ */ jsx( "div", { className: cn("mt-1 text-xs leading-snug text-muted-foreground", (_d = node.style) == null ? void 0 : _d.className), style: inlineStyle, title: ((_e = node.tooltip) == null ? void 0 : _e.plain) ?? text, children: content } ); } function ClickyText({ node }) { var _a, _b; const inlineStyle = toInlineStyle(node.style, node.text ?? node.plain); const content = /* @__PURE__ */ jsx(ClickyInlineContent, { node }); if (!node.style && !node.tooltip) { return /* @__PURE__ */ jsx(Fragment, { children: content }); } return /* @__PURE__ */ jsx( "span", { style: inlineStyle, title: (_a = node.tooltip) == null ? void 0 : _a.plain, className: cn( (_b = node.style) == null ? void 0 : _b.className, (node.text ?? "").includes("\n") && "whitespace-pre-wrap" ), children: content } ); } function ClickyInlineContent({ node }) { var _a; return /* @__PURE__ */ jsxs(Fragment, { children: [ node.text, (_a = node.children) == null ? void 0 : _a.map((child, index) => /* @__PURE__ */ jsx(Fragment$1, { children: /* @__PURE__ */ jsx(ClickyNodeRenderer, { node: child }) }, index)) ] }); } function ClickyLinkNode({ node }) { var _a, _b; const content = /* @__PURE__ */ jsx(ClickyInlineContent, { node }); const inlineStyle = toInlineStyle(node.style, node.text ?? node.plain); const target = browserAnchorTarget(node.target); const rel = target === "_blank" ? "noopener noreferrer" : void 0; if (!node.href) { return /* @__PURE__ */ jsx("span", { style: inlineStyle, children: content }); } return /* @__PURE__ */ jsx( "a", { href: node.href, target, rel, title: (_a = node.tooltip) == null ? void 0 : _a.plain, style: inlineStyle, className: cn((_b = node.style) == null ? void 0 : _b.className, "inline cursor-pointer"), children: content } ); } function ClickyLinkCommandNode({ node }) { var _a, _b, _c, _d, _e, _f, _g, _h; const runtime = useContext(ClickyRuntimeContext); const request = useMemo(() => clickyNodeToCommandRequest(node), [node]); const resolved = useMemo( () => resolveClickyCommand(request, runtime.commandRuntime, runtime.operations), [request, runtime.commandRuntime, runtime.operations] ); const target = request.target ?? "Dialog"; const hasRuntime = runtime.commandRuntime != null; const [dialogOpen, setDialogOpen] = useState(false); const [expanded, setExpanded] = useState(false); if (!hasRuntime) { return /* @__PURE__ */ jsx("span", { title: (_a = node.tooltip) == null ? void 0 : _a.plain, className: cn((_b = node.style) == null ? void 0 : _b.className), children: /* @__PURE__ */ jsx(ClickyInlineContent, { node }) }); } if (target === "_self" || target === "_window" || target === "_tab") { const href = ((_d = (_c = runtime.commandRuntime) == null ? void 0 : _c.hrefForCommand) == null ? void 0 : _d.call(_c, resolved)) ?? buildCommandExecutionHref(resolved); if (!href) { return /* @__PURE__ */ jsx("span", { title: (_e = node.tooltip) == null ? void 0 : _e.plain, className: cn((_f = node.style) == null ? void 0 : _f.className), children: /* @__PURE__ */ jsx(ClickyInlineContent, { node }) }); } return /* @__PURE__ */ jsx( "a", { href, target: browserAnchorTarget(target), rel: target === "_self" ? void 0 : "noopener noreferrer", title: (_g = node.tooltip) == null ? void 0 : _g.plain, style: toInlineStyle(node.style, node.text ?? node.plain), className: cn((_h = node.style) == null ? void 0 : _h.className, "inline cursor-pointer"), children: /* @__PURE__ */ jsx(ClickyInlineContent, { node }) } ); } if (target === "_clicky") { return /* @__PURE__ */ jsx( ClickyLinkCommandTrigger, { node, onClick: () => { var _a2, _b2; return (_b2 = (_a2 = runtime.commandRuntime) == null ? void 0 : _a2.onNavigate) == null ? void 0 : _b2.call(_a2, resolved); } } ); } if (target === "Hover") { return /* @__PURE__ */ jsx( HoverCard, { delay: 75, cardClassName: "w-[28rem] max-w-[calc(100vw-2rem)] whitespace-normal p-0", trigger: /* @__PURE__ */ jsx(ClickyLinkCommandTrigger, { node }), children: /* @__PURE__ */ jsx("div", { className: "max-h-[26rem] overflow-auto p-density-3", children: /* @__PURE__ */ jsx(ClickyAsyncCommandResult, { resolved }) }) } ); } if (target === "Expand") { return /* @__PURE__ */ jsxs("span", { className: "inline", children: [ /* @__PURE__ */ jsx( ClickyLinkCommandTrigger, { node, ariaExpanded: expanded, onClick: () => setExpanded((current) => !current) } ), expanded && /* @__PURE__ */ jsx("div", { className: "mt-2 rounded-md border border-border bg-background p-density-3", children: /* @__PURE__ */ jsx(ClickyAsyncCommandResult, { resolved }) }) ] }); } return /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsx( ClickyLinkCommandTrigger, { node, ariaExpanded: dialogOpen, onClick: () => setDialogOpen(true) } ), /* @__PURE__ */ jsx( ClickyCommandDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), resolved } ) ] }); } function ClickyLinkCommandTrigger({ node, onClick, ariaExpanded }) { var _a, _b; const inlineStyle = toInlineStyle(node.style, node.text ?? node.plain); const handleClick = (event) => { event.preventDefault(); onClick == null ? void 0 : onClick(); }; return /* @__PURE__ */ jsx( "button", { type: "button", title: (_a = node.tooltip) == null ? void 0 : _a.plain, "aria-expanded": ariaExpanded, onClick: handleClick, style: inlineStyle, className: cn( "inline cursor-pointer appearance-none border-0 bg-transparent p-0 text-left align-baseline text-inherit", (_b = node.style) == null ? void 0 : _b.className ), children: /* @__PURE__ */ jsx(ClickyInlineContent, { node }) } ); } function ClickyCommandDialog({ open, onClose, resolved }) { const runtime = useContext(ClickyRuntimeContext); const operation = resolved.operation; const parameters = (operation == null ? void 0 : operation.operation.parameters) ?? []; const initialValues = useMemo( () => buildCommandParameterValues(parameters, resolved.request), [parameters, resolved.request] ); const [result, setResult] = useState(null); const [error, setError] = useState(""); const [isExecuting, setIsExecuting] = useState(false); const [hasAutoStarted, setHasAutoStarted] = useState(false); const shouldAutoRun = open && resolved.request.autoRun === true && operation != null && missingRequiredParameters(parameters, initialValues).length === 0; useEffect(() => { if (!open) { setHasAutoStarted(false); setResult(null); setError(""); } }, [open, resolved.request.command]); async function runCommand(values) { if (!runtime.commandRuntime || !operation) { return; } setIsExecuting(true); setError(""); try { const response = await executeClickyCommand(runtime.commandRuntime.client, operation, values); setResult(response); } catch (err) { setResult(null); setError(err instanceof Error ? err.message : String(err ?? "Unknown error")); } finally { setIsExecuting(false); } } useEffect(() => { if (!shouldAutoRun || hasAutoStarted) { return; } setHasAutoStarted(true); void runCommand(initialValues); }, [hasAutoStarted, initialValues, shouldAutoRun]); return /* @__PURE__ */ jsx( Modal, { open, onClose, title: (operation == null ? void 0 : operation.operation.summary) || resolved.request.command, size: "xl", children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [ operation ? /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 text-xs text-muted-foreground", children: [ /* @__PURE__ */ jsx("span", { className: "rounded-md border border-border bg-muted px-2 py-1 font-medium uppercase", children: operation.method }), /* @__PURE__ */ jsx("code", { children: operation.path }) ] }) : runtime.operationsLoading ? /* @__PURE__ */ jsx(ClickyNotice, { title: "Loading commands", message: "Resolving the command definition." }) : /* @__PURE__ */ jsx( ClickyNotice, { title: "Unknown command", message: `No operation matched "${resolved.request.command}".`, tone: "destructive" } ), operation && /* @__PURE__ */ jsx("div", { className: "rounded-md border border-border bg-muted/20 p-density-3", children: /* @__PURE__ */ jsx( FilterForm, { client: runtime.commandRuntime.client, path: operation.path, method: operation.method, parameters, initialValues, isSubmitting: isExecuting, submitLabel: "Run command", submittingLabel: "Running…", onSubmit: runCommand } ) }), error ? /* @__PURE__ */ jsx(ClickyNotice, { title: "Command failed", message: error, tone: "destructive" }) : /* @__PURE__ */ jsx( ClickyCommandResponse, { response: result, pending: isExecuting, emptyMessage: "Run the command to load a Clicky response." } ) ] }) } ); } function ClickyAsyncCommandResult({ resolved }) { const runtime = useContext(ClickyRuntimeContext); const operation = resolved.operation; const parameters = (operation == null ? void 0 : operation.operation.parameters) ?? []; const initialValues = useMemo( () => buildCommandParameterValues(parameters, resolved.request), [parameters, resolved.request] ); const missing = useMemo( () => missingRequiredParameters(parameters, initialValues), [initialValues, parameters] ); const query = useQuery({ queryKey: [ "clicky-command", resolved.request.command, operation == null ? void 0 : operation.method, operation == null ? void 0 : operation.path, initialValues ], enabled: runtime.commandRuntime != null && operation != null && missing.length === 0, retry: 0, queryFn: async () => executeClickyCommand(runtime.commandRuntime.client, operation, initialValues) }); if (!runtime.commandRuntime) { return /* @__PURE__ */ jsx( ClickyNotice, { title: "Command runtime unavailable", message: "This Clicky view was rendered without a command runtime.", tone: "warning" } ); } if (!operation) { if (runtime.operationsLoading) { return /* @__PURE__ */ jsx(ClickyNotice, { title: "Loading commands", message: "Resolving the command definition." }); } return /* @__PURE__ */ jsx( ClickyNotice, { title: "Unknown command", message: `No operation matched "${resolved.request.command}".`, tone: "destructive" } ); } if (missing.length > 0) { return /* @__PURE__ */ jsx( ClickyNotice, { title: "Missing required parameters", message: `This link is missing: ${missing.map((param) => prettifyName(param.name)).join(", ")}.`, tone: "warning" } ); } if (query.isPending) { return /* @__PURE__ */ jsx(ClickyNotice, { title: "Running command", message: "Loading Clicky response…" }); } if (query.isError) { return /* @__PURE__ */ jsx( ClickyNotice, { title: "Command failed", message: query.error instanceof Error ? query.error.message : "Request failed", tone: "destructive" } ); } return /* @__PURE__ */ jsx(ClickyCommandResponse, { response: query.data, emptyMessage: "No response body returned." }); } function ClickyCommandResponse({ response, pending = false, emptyMessage }) { if (pending) { return /* @__PURE__ */ jsx(ClickyNotice, { title: "Running command", message: "Loading Clicky response…" }); } if (!response) { return /* @__PURE__ */ jsx(ClickyNotice, { title: "No response", message: emptyMessage }); } const parsedPayload = isExecutionResponse(response) ? response.parsed ?? parseJsonBody(response) : response; const rawText = isExecutionResponse(response) ? response.stdout || response.output || response.message || "" : typeof response === "string" ? response : JSON.stringify(response, null, 2); const clickyPayload = typeof parsedPayload === "string" || parsedPayload != null && typeof parsedPayload === "object" ? parsedPayload : rawText; const parsedClicky = clickyPayload === "" ? null : parseClickyData(clickyPayload); if (parsedClicky == null ? void 0 : parsedClicky.ok) { return /* @__PURE__ */ jsx("div", { className: "rounded-md border border-border bg-background p-density-3", children: /* @__PURE__ */ jsx(ClickyNodeRenderer, { node: parsedClicky.document.node }) }); } if (parsedPayload != null && typeof parsedPayload === "object") { return /* @__PURE__ */ jsx(ClickyJsonTree, { value: parsedPayload }); } return /* @__PURE__ */ jsx(ClickyTextPreview, { title: "Response body", content: rawText }); } function ClickyIconNode({ node }) { var _a; const inlineStyle = toInlineStyle(node.style, node.plain ?? node.unicode); return /* @__PURE__ */ jsx("span", { style: inlineStyle, title: (_a = node.tooltip) == null ? void 0 : _a.plain, className: "inline-flex items-center", children: node.iconify ? /* @__PURE__ */ jsx(Icon, { name: node.iconify }) : /* @__PURE__ */ jsx("span", { children: node.unicode ?? node.plain }) }); } function ClickyList({ node }) { const items = node.items ?? []; if (items.length === 0) return null; if (node.inline) { return /* @__PURE__ */ jsx("span", { className: "inline-flex flex-wrap items-start gap-1", children: items.map((item, index) => /* @__PURE__ */ jsxs(Fragment$1, { children: [ index > 0 && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "," }), /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [ !node.ordered && node.bullet && /* @__PURE__ */ jsx(ClickyNodeRenderer, { node: node.bull