UNPKG

@flanksource/clicky-ui

Version:

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

283 lines (282 loc) 11 kB
import { jsxs, jsx } from "react/jsx-runtime"; import { useMemo, useEffect } from "react"; import { ThemeSwitcher } from "../components/theme-switcher.js"; import { ApiExplorer } from "./ApiExplorer.js"; import { getClickySurfaces, makeSurfaceDefinition } from "./clickyMetadata.js"; import { OperationCatalog } from "./OperationCatalog.js"; import { OperationCommandPage } from "./OperationCommandPage.js"; import { OperationEntityPage } from "./OperationEntityPage.js"; import { useOperations } from "./useOperations.js"; function EntityExplorerApp({ client, pathname, renderLink, basePath = "", showApiExplorer = true, apiExplorerMode = "scalar", apiExplorerUrl, apiExplorerConfiguration }) { const { spec } = useOperations(client); const surfaces = useMemo(() => getClickySurfaces(spec), [spec]); const defaultSurface = useMemo( () => surfaces.find((surface) => !surface.admin) ?? surfaces[0], [surfaces] ); const relativePath = useMemo(() => stripBasePath(pathname, basePath), [basePath, pathname]); const route = useMemo(() => parseExplorerRoute(relativePath), [relativePath]); const resolvedRoute = useMemo(() => resolveRouteSurface(route, surfaces), [route, surfaces]); const commandRuntime = useMemo( () => ({ client, hrefForCommand: (resolved) => buildCommandHref(basePath, resolved), onNavigate: (resolved) => { const href = buildCommandHref(basePath, resolved); if (!href || typeof window === "undefined") return; window.history.pushState(window.history.state, "", href); window.dispatchEvent(new PopStateEvent("popstate")); } }), [basePath, client] ); const { initialValues: commandInitialValues, autoRun: commandAutoRun } = useMemo( () => resolvedRoute.kind === "command" ? readCommandQueryParams() : { initialValues: {}, autoRun: false }, [resolvedRoute.kind, pathname] ); const explorerDefinition = useMemo( () => ({ key: "explorer", title: "API Explorer", description: "Every operation exposed by the current OpenAPI spec." }), [] ); useEffect(() => { if (typeof window === "undefined" || defaultSurface == null || relativePath !== "/" || window.location.pathname !== withBasePath(basePath, "/")) { return; } const target = withBasePath(basePath, `/${defaultSurface.key}`); window.history.replaceState(window.history.state, "", target); window.dispatchEvent(new PopStateEvent("popstate")); }, [basePath, defaultSurface, relativePath]); return /* @__PURE__ */ jsxs("div", { className: "flex h-full", children: [ /* @__PURE__ */ jsxs("aside", { className: "flex w-64 shrink-0 flex-col border-r border-border bg-muted/30 p-4", children: [ /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [ /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold", children: (spec == null ? void 0 : spec.info.title) ?? "Clicky Explorer" }), /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: "Metadata-driven entity explorer" }) ] }), /* @__PURE__ */ jsxs("nav", { className: "flex flex-col gap-1", children: [ surfaces.map( (surface) => renderLink({ key: surface.key, to: withBasePath(basePath, `/${surface.key}`), className: navLinkClassName(isSurfaceRouteActive(resolvedRoute, surface.key)), children: surface.title }) ), showApiExplorer && renderLink({ key: explorerDefinition.key, to: withBasePath(basePath, "/explorer"), className: navLinkClassName( resolvedRoute.kind === "explorer" || resolvedRoute.kind === "command" ), children: explorerDefinition.title }) ] }), /* @__PURE__ */ jsx("div", { className: "mt-auto pt-4", children: /* @__PURE__ */ jsx(ThemeSwitcher, {}) }) ] }), /* @__PURE__ */ jsx("main", { className: "flex-1 overflow-auto p-6", children: resolvedRoute.kind === "surface" ? resolvedRoute.surface ? /* @__PURE__ */ jsx( OperationCatalog, { definition: makeSurfaceDefinition(resolvedRoute.surface), entities: [resolvedRoute.surface.entity], client, renderLink, surfaceKey: resolvedRoute.surface.key, commandRuntime } ) : /* @__PURE__ */ jsx(UnknownSurface, { surfaceKey: resolvedRoute.surfaceKey }) : resolvedRoute.kind === "entity" ? resolvedRoute.surface ? /* @__PURE__ */ jsx( OperationEntityPage, { definition: makeSurfaceDefinition(resolvedRoute.surface), entities: [resolvedRoute.surface.entity], client, renderLink, surfaceKey: resolvedRoute.surface.key, backHref: withBasePath(basePath, `/${resolvedRoute.surface.key}`), backLabel: `Back to ${resolvedRoute.surface.title}`, commandRuntime, ...resolvedRoute.id ? { id: resolvedRoute.id } : {} } ) : /* @__PURE__ */ jsx(UnknownSurface, { surfaceKey: resolvedRoute.surfaceKey }) : resolvedRoute.kind === "command" ? /* @__PURE__ */ jsx( OperationCommandPage, { client, backHref: withBasePath(basePath, "/explorer"), backLabel: "Back to API Explorer", renderLink, commandRuntime, initialValues: commandInitialValues, autoRun: commandAutoRun, ...resolvedRoute.operationId ? { operationId: resolvedRoute.operationId } : {} } ) : resolvedRoute.kind === "explorer" ? apiExplorerMode === "scalar" ? /* @__PURE__ */ jsx( ApiExplorer, { ...apiExplorerUrl ? { openApiUrl: apiExplorerUrl } : {}, ...apiExplorerConfiguration ? { configuration: apiExplorerConfiguration } : {} } ) : /* @__PURE__ */ jsx( OperationCatalog, { definition: explorerDefinition, entities: [], allOperations: true, client, renderLink, commandRuntime } ) : defaultSurface == null ? /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "Loading surfaces…" }) : /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "Redirecting…" }) }) ] }); } function parseExplorerRoute(pathname) { const normalized = trimTrailingSlash(pathname) || "/"; if (normalized === "/") { return { kind: "root" }; } const segments = normalized.replace(/^\/+/, "").split("/"); if (segments[0] === "explorer") { return { kind: "explorer" }; } if (segments[0] === "commands") { return segments[1] ? { kind: "command", operationId: segments[1] } : { kind: "command" }; } const surfaceKey = segments[0] ?? ""; if (segments[1]) { return { kind: "entity", surfaceKey, id: decodeURIComponent(segments[1]) }; } return { kind: "surface", surfaceKey }; } function resolveRouteSurface(route, surfaces) { if (route.kind !== "surface" && route.kind !== "entity") { return route; } const surface = surfaces.find((item) => item.key === route.surfaceKey); return surface ? { ...route, surface } : route; } function isSurfaceRouteActive(route, surfaceKey) { return route.kind === "surface" && route.surfaceKey === surfaceKey || route.kind === "entity" && route.surfaceKey === surfaceKey; } function navLinkClassName(active) { return [ "rounded-md px-2 py-1.5 text-sm transition-colors", active ? "bg-accent text-accent-foreground" : "text-foreground hover:bg-accent" ].join(" "); } function stripBasePath(pathname, basePath) { const base = trimTrailingSlash(basePath); if (!base || base === "/") { return trimTrailingSlash(pathname) || "/"; } if (pathname === base) { return "/"; } if (pathname.startsWith(`${base}/`)) { return trimTrailingSlash(pathname.slice(base.length)) || "/"; } return trimTrailingSlash(pathname) || "/"; } function withBasePath(basePath, pathname) { const base = trimTrailingSlash(basePath); if (!base || base === "/") { return pathname; } return `${base}${pathname}`; } function trimTrailingSlash(value) { if (!value) return ""; return value.length > 1 ? value.replace(/\/+$/, "") : value; } function UnknownSurface({ surfaceKey }) { return /* @__PURE__ */ jsxs("div", { className: "p-6 text-sm text-muted-foreground", children: [ "Unknown surface: ", /* @__PURE__ */ jsx("code", { children: surfaceKey }) ] }); } const AUTORUN_QUERY_PARAM = "__autoRun"; const ARG_QUERY_PREFIX = "__arg"; function buildCommandHref(basePath, resolved) { var _a, _b; const commandId = ((_a = resolved.operation) == null ? void 0 : _a.operation.operationId) ?? resolved.request.command; if (!commandId) return void 0; const meta = (_b = resolved.operation) == null ? void 0 : _b.operation["x-clicky"]; const args = resolved.request.args ?? []; const flags = resolved.request.flags ?? {}; if ((meta == null ? void 0 : meta.verb) === "get" && (meta == null ? void 0 : meta.scope) === "entity" && meta.surface && args[0]) { const search2 = new URLSearchParams(); for (const [key, value] of Object.entries(flags)) { if (value !== void 0 && value !== "") { search2.set(key, value); } } const query2 = search2.toString(); const suffix2 = query2 ? `?${query2}` : ""; return withBasePath( basePath, `/${encodeURIComponent(meta.surface)}/${encodeURIComponent(args[0])}${suffix2}` ); } const search = new URLSearchParams(); args.forEach((value, index) => { if (value !== void 0 && value !== "") { search.set(`${ARG_QUERY_PREFIX}${index}`, value); } }); for (const [key, value] of Object.entries(flags)) { if (value !== void 0 && value !== "") { search.set(key, value); } } if (resolved.request.autoRun) { search.set(AUTORUN_QUERY_PARAM, "1"); } const query = search.toString(); const suffix = query ? `?${query}` : ""; return withBasePath(basePath, `/commands/${encodeURIComponent(commandId)}${suffix}`); } function readCommandQueryParams() { if (typeof window === "undefined") { return { initialValues: {}, autoRun: false }; } const search = new URLSearchParams(window.location.search); const initialValues = {}; let autoRun = false; const positional = []; for (const [key, value] of search.entries()) { if (key === AUTORUN_QUERY_PARAM) { autoRun = value === "1" || value === "true"; continue; } if (key.startsWith(ARG_QUERY_PREFIX)) { const index = Number.parseInt(key.slice(ARG_QUERY_PREFIX.length), 10); if (Number.isFinite(index) && index >= 0) { positional[index] = value; } continue; } initialValues[key] = value; } const packedArgs = positional.filter((value) => value !== void 0).join(" "); if (packedArgs !== "") { initialValues.args = packedArgs; } return { initialValues, autoRun }; } export { EntityExplorerApp }; //# sourceMappingURL=EntityExplorerApp.js.map