api_binding_ui 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,7 +12,7 @@ import {
12
12
  createEngine2,
13
13
  validateRegistry2,
14
14
  useBindingRuntime
15
- } from "../index-e9twc25y.js";
15
+ } from "../index-3q1vx729.js";
16
16
 
17
17
  // src/studio/BindingStudio.tsx
18
18
  import * as React15 from "react";
@@ -2096,10 +2096,96 @@ function docsSummary(c) {
2096
2096
  if (c.status === "error")
2097
2097
  return c.error;
2098
2098
  const products = new Set(c.catalog.operations.map((o) => o.product).filter(Boolean));
2099
- return `${products.size || 1} ${products.size === 1 ? "product" : "products"} · ${c.catalog.operations.length} APIs · loaded ${TIME.format(c.loadedAt)}`;
2099
+ const where = c.source === "pasted" ? "Pasted file · " : "";
2100
+ return `${where}${products.size || 1} ${products.size === 1 ? "product" : "products"} · ${c.catalog.operations.length} ${c.catalog.operations.length === 1 ? "API" : "APIs"} · loaded ${TIME.format(c.loadedAt)}`;
2100
2101
  }
2101
- function SettingsPanel({ docsUrl, onDocsUrl, catalog, onReload, manifests, onImport, onClose }) {
2102
+ var SIZE = new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 });
2103
+ var kb = (chars) => chars < 1e6 ? `${SIZE.format(chars / 1000)} KB` : `${SIZE.format(chars / 1e6)} MB`;
2104
+ function PasteDocs({ onPaste, onDone }) {
2105
+ const area = React12.useRef(null);
2106
+ const [size, setSize] = React12.useState(0);
2107
+ const [busy, setBusy] = React12.useState(false);
2108
+ const [error, setError] = React12.useState();
2109
+ const load = async () => {
2110
+ const text = area.current?.value ?? "";
2111
+ setBusy(true);
2112
+ setError(undefined);
2113
+ const problem = await onPaste(text);
2114
+ setBusy(false);
2115
+ if (problem) {
2116
+ setError(problem);
2117
+ return;
2118
+ }
2119
+ if (area.current)
2120
+ area.current.value = "";
2121
+ onDone();
2122
+ };
2123
+ return /* @__PURE__ */ jsxs15("div", {
2124
+ className: "abs-paste",
2125
+ children: [
2126
+ /* @__PURE__ */ jsx17("textarea", {
2127
+ ref: area,
2128
+ id: "abs-docs-paste",
2129
+ className: "abs-input abs-paste-area",
2130
+ rows: 7,
2131
+ wrap: "off",
2132
+ spellCheck: false,
2133
+ autoCorrect: "off",
2134
+ autoCapitalize: "off",
2135
+ autoComplete: "off",
2136
+ autoFocus: true,
2137
+ placeholder: "Paste the contents of index.yaml (or the JSON version)",
2138
+ "aria-label": "OpenAPI document",
2139
+ "aria-describedby": "abs-paste-note",
2140
+ "aria-invalid": !!error,
2141
+ onInput: (e) => setSize(e.currentTarget.value.length)
2142
+ }),
2143
+ /* @__PURE__ */ jsxs15("div", {
2144
+ className: "abs-set-row",
2145
+ children: [
2146
+ /* @__PURE__ */ jsx17("button", {
2147
+ type: "button",
2148
+ className: "abs-btn abs-btn-primary",
2149
+ onClick: () => void load(),
2150
+ disabled: busy || size === 0,
2151
+ children: busy ? /* @__PURE__ */ jsxs15(Fragment9, {
2152
+ children: [
2153
+ /* @__PURE__ */ jsx17(Icon, {
2154
+ name: "refresh",
2155
+ size: 14,
2156
+ className: "abs-spin"
2157
+ }),
2158
+ "Reading…"
2159
+ ]
2160
+ }) : "Load"
2161
+ }),
2162
+ /* @__PURE__ */ jsx17("button", {
2163
+ type: "button",
2164
+ className: "abs-btn abs-btn-ghost",
2165
+ onClick: onDone,
2166
+ disabled: busy,
2167
+ children: "Cancel"
2168
+ }),
2169
+ /* @__PURE__ */ jsx17("span", {
2170
+ className: "abs-paste-size",
2171
+ "aria-live": "polite",
2172
+ children: size ? kb(size) : ""
2173
+ })
2174
+ ]
2175
+ }),
2176
+ /* @__PURE__ */ jsx17("p", {
2177
+ id: "abs-paste-note",
2178
+ className: "abs-set-status" + (error ? " is-error" : ""),
2179
+ role: error ? "alert" : undefined,
2180
+ children: error ?? "Used for this session only. Nothing is saved; the URL is used again next time."
2181
+ })
2182
+ ]
2183
+ });
2184
+ }
2185
+ function SettingsPanel({ docsUrl, onDocsUrl, catalog, onReload, onPaste, manifests, onImport, onClose }) {
2102
2186
  const [draft, setDraft] = React12.useState(docsUrl);
2187
+ const [pasting, setPasting] = React12.useState(false);
2188
+ const usingPaste = catalog.status === "ready" && catalog.source === "pasted";
2103
2189
  const [importMsg, setImportMsg] = React12.useState(null);
2104
2190
  const panel = React12.useRef(null);
2105
2191
  const file = React12.useRef(null);
@@ -2213,15 +2299,46 @@ function SettingsPanel({ docsUrl, onDocsUrl, catalog, onReload, manifests, onImp
2213
2299
  "aria-live": "polite",
2214
2300
  children: valid ? docsSummary(catalog) : "Enter a full URL, for example " + DEFAULT_DOCS_URL
2215
2301
  }),
2216
- docsUrl !== DEFAULT_DOCS_URL ? /* @__PURE__ */ jsx17("button", {
2217
- type: "button",
2218
- className: "abs-link abs-more",
2219
- onClick: () => {
2220
- setDraft(DEFAULT_DOCS_URL);
2221
- onDocsUrl(DEFAULT_DOCS_URL);
2222
- },
2223
- children: "Use the default URL"
2224
- }) : null
2302
+ pasting ? /* @__PURE__ */ jsx17(PasteDocs, {
2303
+ onPaste,
2304
+ onDone: () => setPasting(false)
2305
+ }) : /* @__PURE__ */ jsxs15("div", {
2306
+ className: "abs-set-links",
2307
+ children: [
2308
+ usingPaste ? /* @__PURE__ */ jsxs15("button", {
2309
+ type: "button",
2310
+ className: "abs-link",
2311
+ onClick: onReload,
2312
+ children: [
2313
+ /* @__PURE__ */ jsx17(Icon, {
2314
+ name: "refresh",
2315
+ size: 14
2316
+ }),
2317
+ "Use the URL again"
2318
+ ]
2319
+ }) : /* @__PURE__ */ jsxs15("button", {
2320
+ type: "button",
2321
+ className: "abs-link",
2322
+ onClick: () => setPasting(true),
2323
+ children: [
2324
+ /* @__PURE__ */ jsx17(Icon, {
2325
+ name: "upload",
2326
+ size: 14
2327
+ }),
2328
+ "Paste the file instead"
2329
+ ]
2330
+ }),
2331
+ docsUrl !== DEFAULT_DOCS_URL ? /* @__PURE__ */ jsx17("button", {
2332
+ type: "button",
2333
+ className: "abs-link",
2334
+ onClick: () => {
2335
+ setDraft(DEFAULT_DOCS_URL);
2336
+ onDocsUrl(DEFAULT_DOCS_URL);
2337
+ },
2338
+ children: "Use the default URL"
2339
+ }) : null
2340
+ ]
2341
+ })
2225
2342
  ]
2226
2343
  }),
2227
2344
  /* @__PURE__ */ jsxs15("div", {
@@ -2364,13 +2481,29 @@ import * as React14 from "react";
2364
2481
  // src/framework/loadCatalog.ts
2365
2482
  import { parse } from "yaml";
2366
2483
  class CatalogLoadError extends Error {
2367
- url;
2368
- constructor(message, url) {
2484
+ source;
2485
+ constructor(message, source) {
2369
2486
  super(message);
2370
- this.url = url;
2487
+ this.source = source;
2371
2488
  this.name = "CatalogLoadError";
2372
2489
  }
2373
2490
  }
2491
+ function catalogFromText(text, source) {
2492
+ const body = text.trim();
2493
+ if (!body)
2494
+ throw new CatalogLoadError("Nothing to read: the text is empty.", source);
2495
+ let doc;
2496
+ try {
2497
+ doc = body.startsWith("{") ? JSON.parse(body) : parse(body);
2498
+ } catch {
2499
+ throw new CatalogLoadError("The text is not valid JSON or YAML.", source);
2500
+ }
2501
+ try {
2502
+ return catalogFromOpenApi2(doc, source.includes("://") ? { url: source } : {});
2503
+ } catch (e) {
2504
+ throw new CatalogLoadError(e instanceof Error ? e.message : "The text is not an OpenAPI document.", source);
2505
+ }
2506
+ }
2374
2507
  async function loadCatalog(url, init) {
2375
2508
  let res;
2376
2509
  try {
@@ -2380,39 +2513,46 @@ async function loadCatalog(url, init) {
2380
2513
  }
2381
2514
  if (!res.ok)
2382
2515
  throw new CatalogLoadError(`The docs server answered ${res.status} ${res.statusText}. Check the URL.`, url);
2383
- const text = await res.text();
2384
- let doc;
2385
- try {
2386
- doc = text.trimStart().startsWith("{") ? JSON.parse(text) : parse(text);
2387
- } catch {
2388
- throw new CatalogLoadError("The file is not valid JSON or YAML.", url);
2389
- }
2390
- try {
2391
- return catalogFromOpenApi2(doc, { url });
2392
- } catch (e) {
2393
- throw new CatalogLoadError(e instanceof Error ? e.message : "The file is not an OpenAPI document.", url);
2394
- }
2516
+ return catalogFromText(await res.text(), url);
2395
2517
  }
2396
2518
 
2397
2519
  // src/studio/useCatalog.ts
2398
2520
  function useCatalog(url, provided) {
2399
- const [state, setState] = React14.useState(() => provided ? { status: "ready", catalog: provided, loadedAt: new Date } : { status: "loading" });
2521
+ const [state, setState] = React14.useState(() => provided ? { status: "ready", catalog: provided, loadedAt: new Date, source: "provided" } : { status: "loading" });
2400
2522
  const [nonce, setNonce] = React14.useState(0);
2523
+ const [pasted, setPasted] = React14.useState(null);
2401
2524
  React14.useEffect(() => {
2525
+ if (pasted) {
2526
+ setState({ status: "ready", catalog: pasted, loadedAt: new Date, source: "pasted" });
2527
+ return;
2528
+ }
2402
2529
  if (provided) {
2403
- setState({ status: "ready", catalog: provided, loadedAt: new Date });
2530
+ setState({ status: "ready", catalog: provided, loadedAt: new Date, source: "provided" });
2404
2531
  return;
2405
2532
  }
2406
2533
  const ctrl = new AbortController;
2407
2534
  setState((s) => ({ status: "loading", catalog: s.catalog }));
2408
- loadCatalog(url, { signal: ctrl.signal }).then((catalog) => setState({ status: "ready", catalog, loadedAt: new Date }), (e) => {
2535
+ loadCatalog(url, { signal: ctrl.signal }).then((catalog) => setState({ status: "ready", catalog, loadedAt: new Date, source: "url" }), (e) => {
2409
2536
  if (!ctrl.signal.aborted)
2410
2537
  setState((s) => ({ status: "error", catalog: s.catalog, error: e instanceof Error ? e.message : String(e) }));
2411
2538
  });
2412
2539
  return () => ctrl.abort();
2413
- }, [url, provided, nonce]);
2414
- const reload = React14.useCallback(() => setNonce((n) => n + 1), []);
2415
- return [state, reload];
2540
+ }, [url, provided, nonce, pasted]);
2541
+ const reload = React14.useCallback(() => {
2542
+ setPasted(null);
2543
+ setNonce((n) => n + 1);
2544
+ }, []);
2545
+ const loadPasted = React14.useCallback((text) => new Promise((resolve) => {
2546
+ setTimeout(() => {
2547
+ try {
2548
+ setPasted(catalogFromText(text, "pasted"));
2549
+ resolve(undefined);
2550
+ } catch (e) {
2551
+ resolve(e instanceof Error ? e.message : String(e));
2552
+ }
2553
+ }, 0);
2554
+ }), []);
2555
+ return [state, reload, loadPasted];
2416
2556
  }
2417
2557
 
2418
2558
  // src/studio/BindingStudio.tsx
@@ -2494,7 +2634,7 @@ var EMPTY_HINT = /* @__PURE__ */ jsxs16("aside", {
2494
2634
  });
2495
2635
  function BindingStudio({ framework, docsUrl = DEFAULT_DOCS_URL, persistence, initialScreen, title = "Bind API", onClose }) {
2496
2636
  const [settings, updateSettings] = useSettings({ docsUrl });
2497
- const [catalogState, reloadCatalog] = useCatalog(settings.docsUrl, framework.catalog);
2637
+ const [catalogState, reloadCatalog, loadPasted] = useCatalog(settings.docsUrl, framework.catalog);
2498
2638
  const [settingsOpen, setSettingsOpen] = React15.useState(false);
2499
2639
  const closeSettings = React15.useCallback(() => setSettingsOpen(false), []);
2500
2640
  const adapter = React15.useMemo(() => persistence ?? localStorageAdapter2(), [persistence]);
@@ -2534,6 +2674,7 @@ function BindingStudio({ framework, docsUrl = DEFAULT_DOCS_URL, persistence, ini
2534
2674
  onDocsUrl: (url) => updateSettings({ docsUrl: url }),
2535
2675
  catalog: catalogState,
2536
2676
  onReload: reloadCatalog,
2677
+ onPaste: loadPasted,
2537
2678
  manifests: () => fw ? Object.entries(binds.index).map(([s, b]) => toManifest2(fw, s, b)) : [],
2538
2679
  onImport: binds.importManifests,
2539
2680
  onClose: closeSettings
@@ -2591,7 +2732,7 @@ function BindingStudio({ framework, docsUrl = DEFAULT_DOCS_URL, persistence, ini
2591
2732
  type: "button",
2592
2733
  className: "abs-btn",
2593
2734
  onClick: () => setSettingsOpen(true),
2594
- children: "Change the URL"
2735
+ children: "Change the URL or paste the file"
2595
2736
  })
2596
2737
  ]
2597
2738
  })
@@ -2802,5 +2943,5 @@ export {
2802
2943
  BindingWidget
2803
2944
  };
2804
2945
 
2805
- //# debugId=34079C9B227D68B264756E2164756E21
2946
+ //# debugId=C512BDFB06A7F43E64756E2164756E21
2806
2947
  //# sourceMappingURL=index.js.map