api_binding_ui 2.0.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -50
- package/SKILL.md +29 -34
- package/dist/app/index-0n23yy5r.js +367 -0
- package/dist/app/index-aaxy714b.css +1 -0
- package/dist/app/index.html +7 -0
- package/dist/cli.js +969 -0
- package/dist/codegen/generate.d.ts +22 -0
- package/dist/codegen/helpers.d.ts +2 -0
- package/dist/framework/contract.d.ts +38 -23
- package/dist/framework/expr.d.ts +26 -27
- package/dist/framework/file.d.ts +28 -0
- package/dist/framework/store.d.ts +3 -15
- package/dist/{index-3q1vx729.js → index-jj8twdw9.js} +158 -173
- package/dist/index-jj8twdw9.js.map +16 -0
- package/dist/index.d.ts +6 -8
- package/dist/index.js +461 -298
- package/dist/index.js.map +6 -8
- package/dist/studio/BindingStudio.d.ts +6 -11
- package/dist/studio/SettingsPanel.d.ts +3 -7
- package/dist/studio/index.css +0 -149
- package/dist/studio/index.d.ts +1 -2
- package/dist/studio/index.js +228 -437
- package/dist/studio/index.js.map +8 -10
- package/dist/studio/useBindings.d.ts +7 -5
- package/dist/studio/useCatalog.d.ts +2 -0
- package/package.json +65 -53
- package/spec/FRAMEWORK.md +106 -201
- package/dist/framework/runner.d.ts +0 -36
- package/dist/index-3q1vx729.js.map +0 -16
- package/dist/runtime/BindingProvider.d.ts +0 -42
- package/dist/runtime/BindingScreen.d.ts +0 -23
- package/dist/runtime/hooks.d.ts +0 -22
- package/dist/runtime/screenStore.d.ts +0 -7
- package/dist/studio/BindingWidget.d.ts +0 -9
- package/dist/studio/useSettings.d.ts +0 -8
package/dist/studio/index.js
CHANGED
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
toManifest2,
|
|
5
|
-
markDirty,
|
|
6
|
-
savePending,
|
|
7
|
-
loadAll,
|
|
8
|
-
putBinding,
|
|
9
|
-
dropBinding,
|
|
10
|
-
parseManifests,
|
|
2
|
+
catalogFromText2,
|
|
3
|
+
loadCatalog2,
|
|
11
4
|
humanize,
|
|
5
|
+
fromFile2,
|
|
6
|
+
toFile2,
|
|
12
7
|
createEngine2,
|
|
13
|
-
validateRegistry2
|
|
14
|
-
|
|
15
|
-
} from "../index-3q1vx729.js";
|
|
8
|
+
validateRegistry2
|
|
9
|
+
} from "../index-jj8twdw9.js";
|
|
16
10
|
|
|
17
11
|
// src/studio/BindingStudio.tsx
|
|
18
|
-
import * as
|
|
12
|
+
import * as React14 from "react";
|
|
19
13
|
|
|
20
14
|
// src/studio/ComponentList.tsx
|
|
21
15
|
import * as React2 from "react";
|
|
@@ -2052,44 +2046,53 @@ function Navigator({ engine, current, onPick }) {
|
|
|
2052
2046
|
// src/studio/SettingsPanel.tsx
|
|
2053
2047
|
import * as React12 from "react";
|
|
2054
2048
|
|
|
2055
|
-
// src/studio/
|
|
2049
|
+
// src/studio/useCatalog.ts
|
|
2056
2050
|
import * as React11 from "react";
|
|
2057
2051
|
var DEFAULT_DOCS_URL = "https://apidocs.highradius.com/documentation-ui/index.yaml";
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2052
|
+
function useCatalog(url, provided) {
|
|
2053
|
+
const [state, setState] = React11.useState(() => provided ? { status: "ready", catalog: provided, loadedAt: new Date, source: "provided" } : { status: "loading" });
|
|
2054
|
+
const [nonce, setNonce] = React11.useState(0);
|
|
2055
|
+
const [pasted, setPasted] = React11.useState(null);
|
|
2056
|
+
React11.useEffect(() => {
|
|
2057
|
+
if (pasted) {
|
|
2058
|
+
setState({ status: "ready", catalog: pasted, loadedAt: new Date, source: "pasted" });
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
2061
|
+
if (provided) {
|
|
2062
|
+
setState({ status: "ready", catalog: provided, loadedAt: new Date, source: "provided" });
|
|
2063
|
+
return;
|
|
2064
|
+
}
|
|
2065
|
+
if (!url)
|
|
2066
|
+
return;
|
|
2067
|
+
const ctrl = new AbortController;
|
|
2068
|
+
setState((s) => ({ status: "loading", catalog: s.catalog }));
|
|
2069
|
+
loadCatalog2(url, { signal: ctrl.signal }).then((catalog) => setState({ status: "ready", catalog, loadedAt: new Date, source: "url" }), (e) => {
|
|
2070
|
+
if (!ctrl.signal.aborted)
|
|
2071
|
+
setState((s) => ({ status: "error", catalog: s.catalog, error: e instanceof Error ? e.message : String(e) }));
|
|
2077
2072
|
});
|
|
2073
|
+
return () => ctrl.abort();
|
|
2074
|
+
}, [url, provided, nonce, pasted]);
|
|
2075
|
+
const reload = React11.useCallback(() => {
|
|
2076
|
+
setPasted(null);
|
|
2077
|
+
setNonce((n) => n + 1);
|
|
2078
2078
|
}, []);
|
|
2079
|
-
|
|
2079
|
+
const loadPasted = React11.useCallback((text) => new Promise((resolve) => {
|
|
2080
|
+
setTimeout(() => {
|
|
2081
|
+
try {
|
|
2082
|
+
setPasted(catalogFromText2(text, "pasted"));
|
|
2083
|
+
resolve(undefined);
|
|
2084
|
+
} catch (e) {
|
|
2085
|
+
resolve(e instanceof Error ? e.message : String(e));
|
|
2086
|
+
}
|
|
2087
|
+
}, 0);
|
|
2088
|
+
}), []);
|
|
2089
|
+
return [state, reload, loadPasted];
|
|
2080
2090
|
}
|
|
2081
2091
|
|
|
2082
2092
|
// src/studio/SettingsPanel.tsx
|
|
2083
2093
|
import { jsx as jsx17, jsxs as jsxs15, Fragment as Fragment9 } from "react/jsx-runtime";
|
|
2084
2094
|
var TIME = new Intl.DateTimeFormat(undefined, { hour: "numeric", minute: "2-digit" });
|
|
2085
|
-
|
|
2086
|
-
try {
|
|
2087
|
-
const u = new URL(v, location.href);
|
|
2088
|
-
return u.protocol === "https:" || u.protocol === "http:" || u.protocol === "blob:";
|
|
2089
|
-
} catch {
|
|
2090
|
-
return false;
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2095
|
+
var isSource = (v) => v.trim().length > 0 && !/^\s*(javascript|data):/i.test(v);
|
|
2093
2096
|
function docsSummary(c) {
|
|
2094
2097
|
if (c.status === "loading")
|
|
2095
2098
|
return "Loading…";
|
|
@@ -2182,14 +2185,12 @@ function PasteDocs({ onPaste, onDone }) {
|
|
|
2182
2185
|
]
|
|
2183
2186
|
});
|
|
2184
2187
|
}
|
|
2185
|
-
function SettingsPanel({ docsUrl, onDocsUrl, catalog, onReload, onPaste,
|
|
2188
|
+
function SettingsPanel({ docsUrl, onDocsUrl, catalog, onReload, onPaste, onClose }) {
|
|
2186
2189
|
const [draft, setDraft] = React12.useState(docsUrl);
|
|
2187
2190
|
const [pasting, setPasting] = React12.useState(false);
|
|
2188
2191
|
const usingPaste = catalog.status === "ready" && catalog.source === "pasted";
|
|
2189
|
-
const [importMsg, setImportMsg] = React12.useState(null);
|
|
2190
2192
|
const panel = React12.useRef(null);
|
|
2191
|
-
const
|
|
2192
|
-
const valid = isUrl(draft);
|
|
2193
|
+
const valid = isSource(draft);
|
|
2193
2194
|
const dirty = draft.trim() !== docsUrl;
|
|
2194
2195
|
React12.useEffect(() => {
|
|
2195
2196
|
const key = (e) => {
|
|
@@ -2218,223 +2219,144 @@ function SettingsPanel({ docsUrl, onDocsUrl, catalog, onReload, onPaste, manifes
|
|
|
2218
2219
|
else
|
|
2219
2220
|
onReload();
|
|
2220
2221
|
};
|
|
2221
|
-
|
|
2222
|
-
const blob = new Blob([JSON.stringify(manifests(), null, 2) + `
|
|
2223
|
-
`], { type: "application/json" });
|
|
2224
|
-
const a = document.createElement("a");
|
|
2225
|
-
a.href = URL.createObjectURL(blob);
|
|
2226
|
-
a.download = "bindings.json";
|
|
2227
|
-
a.click();
|
|
2228
|
-
setTimeout(() => URL.revokeObjectURL(a.href), 1000);
|
|
2229
|
-
};
|
|
2230
|
-
const readFile = async (f) => {
|
|
2231
|
-
if (!f)
|
|
2232
|
-
return;
|
|
2233
|
-
try {
|
|
2234
|
-
const list = parseManifests(await f.text());
|
|
2235
|
-
onImport(list);
|
|
2236
|
-
setImportMsg({ ok: true, text: `Imported ${list.length} ${list.length === 1 ? "screen" : "screens"}.` });
|
|
2237
|
-
} catch (err) {
|
|
2238
|
-
setImportMsg({ ok: false, text: err instanceof Error ? err.message : "The file could not be read." });
|
|
2239
|
-
} finally {
|
|
2240
|
-
if (file.current)
|
|
2241
|
-
file.current.value = "";
|
|
2242
|
-
}
|
|
2243
|
-
};
|
|
2244
|
-
return /* @__PURE__ */ jsxs15("div", {
|
|
2222
|
+
return /* @__PURE__ */ jsx17("div", {
|
|
2245
2223
|
className: "abs-settings abs-pop",
|
|
2246
2224
|
ref: panel,
|
|
2247
2225
|
role: "dialog",
|
|
2248
2226
|
"aria-label": "Settings",
|
|
2249
|
-
children:
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
children: dirty ? "Use" : /* @__PURE__ */ jsxs15(Fragment9, {
|
|
2284
|
-
children: [
|
|
2285
|
-
/* @__PURE__ */ jsx17(Icon, {
|
|
2286
|
-
name: "refresh",
|
|
2287
|
-
size: 14,
|
|
2288
|
-
className: catalog.status === "loading" ? "abs-spin" : ""
|
|
2289
|
-
}),
|
|
2290
|
-
"Reload"
|
|
2291
|
-
]
|
|
2292
|
-
})
|
|
2293
|
-
})
|
|
2294
|
-
]
|
|
2295
|
-
}),
|
|
2296
|
-
/* @__PURE__ */ jsx17("p", {
|
|
2297
|
-
id: "abs-docs-status",
|
|
2298
|
-
className: "abs-set-status" + (catalog.status === "error" ? " is-error" : ""),
|
|
2299
|
-
"aria-live": "polite",
|
|
2300
|
-
children: valid ? docsSummary(catalog) : "Enter a full URL, for example " + DEFAULT_DOCS_URL
|
|
2301
|
-
}),
|
|
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,
|
|
2227
|
+
children: /* @__PURE__ */ jsxs15("form", {
|
|
2228
|
+
className: "abs-set-sec",
|
|
2229
|
+
onSubmit: apply,
|
|
2230
|
+
children: [
|
|
2231
|
+
/* @__PURE__ */ jsx17("label", {
|
|
2232
|
+
className: "abs-set-h",
|
|
2233
|
+
htmlFor: "abs-docs-url",
|
|
2234
|
+
children: "API docs"
|
|
2235
|
+
}),
|
|
2236
|
+
/* @__PURE__ */ jsx17("p", {
|
|
2237
|
+
className: "abs-set-d",
|
|
2238
|
+
children: "The OpenAPI file every API is read from: a URL, or a path next to the bindings file."
|
|
2239
|
+
}),
|
|
2240
|
+
/* @__PURE__ */ jsxs15("div", {
|
|
2241
|
+
className: "abs-set-row",
|
|
2242
|
+
children: [
|
|
2243
|
+
/* @__PURE__ */ jsx17("input", {
|
|
2244
|
+
id: "abs-docs-url",
|
|
2245
|
+
name: "docsUrl",
|
|
2246
|
+
type: "text",
|
|
2247
|
+
inputMode: "url",
|
|
2248
|
+
autoComplete: "off",
|
|
2249
|
+
spellCheck: false,
|
|
2250
|
+
className: "abs-input" + (valid ? "" : " is-invalid"),
|
|
2251
|
+
value: draft,
|
|
2252
|
+
onChange: (e) => setDraft(e.target.value),
|
|
2253
|
+
"aria-invalid": !valid,
|
|
2254
|
+
"aria-describedby": "abs-docs-status"
|
|
2255
|
+
}),
|
|
2256
|
+
/* @__PURE__ */ jsx17("button", {
|
|
2257
|
+
type: "submit",
|
|
2258
|
+
className: "abs-btn abs-btn-primary",
|
|
2259
|
+
disabled: !valid || catalog.status === "loading",
|
|
2260
|
+
children: dirty ? "Use" : /* @__PURE__ */ jsxs15(Fragment9, {
|
|
2312
2261
|
children: [
|
|
2313
2262
|
/* @__PURE__ */ jsx17(Icon, {
|
|
2314
2263
|
name: "refresh",
|
|
2315
|
-
size: 14
|
|
2264
|
+
size: 14,
|
|
2265
|
+
className: catalog.status === "loading" ? "abs-spin" : ""
|
|
2316
2266
|
}),
|
|
2317
|
-
"
|
|
2267
|
+
"Reload"
|
|
2318
2268
|
]
|
|
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
|
-
})
|
|
2342
|
-
]
|
|
2343
|
-
}),
|
|
2344
|
-
/* @__PURE__ */ jsxs15("div", {
|
|
2345
|
-
className: "abs-set-sec",
|
|
2346
|
-
children: [
|
|
2347
|
-
/* @__PURE__ */ jsx17("span", {
|
|
2348
|
-
className: "abs-set-h",
|
|
2349
|
-
children: "Bindings"
|
|
2350
|
-
}),
|
|
2351
|
-
/* @__PURE__ */ jsx17("p", {
|
|
2352
|
-
className: "abs-set-d",
|
|
2353
|
-
children: "Download to commit them with the app. Import replaces the screens the file contains."
|
|
2354
|
-
}),
|
|
2355
|
-
/* @__PURE__ */ jsxs15("div", {
|
|
2356
|
-
className: "abs-set-row",
|
|
2357
|
-
children: [
|
|
2358
|
-
/* @__PURE__ */ jsxs15("button", {
|
|
2359
|
-
type: "button",
|
|
2360
|
-
className: "abs-btn",
|
|
2361
|
-
onClick: download,
|
|
2362
|
-
children: [
|
|
2363
|
-
/* @__PURE__ */ jsx17(Icon, {
|
|
2364
|
-
name: "download",
|
|
2365
|
-
size: 14
|
|
2366
|
-
}),
|
|
2367
|
-
"Download"
|
|
2368
|
-
]
|
|
2369
|
-
}),
|
|
2370
|
-
/* @__PURE__ */ jsxs15("button", {
|
|
2371
|
-
type: "button",
|
|
2372
|
-
className: "abs-btn",
|
|
2373
|
-
onClick: () => file.current?.click(),
|
|
2374
|
-
children: [
|
|
2375
|
-
/* @__PURE__ */ jsx17(Icon, {
|
|
2376
|
-
name: "upload",
|
|
2377
|
-
size: 14
|
|
2378
|
-
}),
|
|
2379
|
-
"Import…"
|
|
2380
|
-
]
|
|
2381
|
-
}),
|
|
2382
|
-
/* @__PURE__ */ jsx17("input", {
|
|
2383
|
-
ref: file,
|
|
2384
|
-
type: "file",
|
|
2385
|
-
accept: "application/json,.json",
|
|
2386
|
-
hidden: true,
|
|
2387
|
-
onChange: (e) => void readFile(e.target.files?.[0])
|
|
2388
2269
|
})
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2270
|
+
})
|
|
2271
|
+
]
|
|
2272
|
+
}),
|
|
2273
|
+
/* @__PURE__ */ jsx17("p", {
|
|
2274
|
+
id: "abs-docs-status",
|
|
2275
|
+
className: "abs-set-status" + (catalog.status === "error" ? " is-error" : ""),
|
|
2276
|
+
"aria-live": "polite",
|
|
2277
|
+
children: valid ? docsSummary(catalog) : "Enter a URL or a path, for example " + DEFAULT_DOCS_URL
|
|
2278
|
+
}),
|
|
2279
|
+
pasting ? /* @__PURE__ */ jsx17(PasteDocs, {
|
|
2280
|
+
onPaste,
|
|
2281
|
+
onDone: () => setPasting(false)
|
|
2282
|
+
}) : /* @__PURE__ */ jsxs15("div", {
|
|
2283
|
+
className: "abs-set-links",
|
|
2284
|
+
children: [
|
|
2285
|
+
usingPaste ? /* @__PURE__ */ jsxs15("button", {
|
|
2286
|
+
type: "button",
|
|
2287
|
+
className: "abs-link",
|
|
2288
|
+
onClick: onReload,
|
|
2289
|
+
children: [
|
|
2290
|
+
/* @__PURE__ */ jsx17(Icon, {
|
|
2291
|
+
name: "refresh",
|
|
2292
|
+
size: 14
|
|
2293
|
+
}),
|
|
2294
|
+
"Use the URL again"
|
|
2295
|
+
]
|
|
2296
|
+
}) : /* @__PURE__ */ jsxs15("button", {
|
|
2297
|
+
type: "button",
|
|
2298
|
+
className: "abs-link",
|
|
2299
|
+
onClick: () => setPasting(true),
|
|
2300
|
+
children: [
|
|
2301
|
+
/* @__PURE__ */ jsx17(Icon, {
|
|
2302
|
+
name: "upload",
|
|
2303
|
+
size: 14
|
|
2304
|
+
}),
|
|
2305
|
+
"Paste the file instead"
|
|
2306
|
+
]
|
|
2307
|
+
}),
|
|
2308
|
+
docsUrl !== DEFAULT_DOCS_URL ? /* @__PURE__ */ jsx17("button", {
|
|
2309
|
+
type: "button",
|
|
2310
|
+
className: "abs-link",
|
|
2311
|
+
onClick: () => {
|
|
2312
|
+
setDraft(DEFAULT_DOCS_URL);
|
|
2313
|
+
onDocsUrl(DEFAULT_DOCS_URL);
|
|
2314
|
+
},
|
|
2315
|
+
children: "Use the default URL"
|
|
2316
|
+
}) : null
|
|
2317
|
+
]
|
|
2318
|
+
})
|
|
2319
|
+
]
|
|
2320
|
+
})
|
|
2399
2321
|
});
|
|
2400
2322
|
}
|
|
2401
2323
|
|
|
2402
2324
|
// src/studio/useBindings.ts
|
|
2403
2325
|
import * as React13 from "react";
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2326
|
+
|
|
2327
|
+
// src/framework/store.ts
|
|
2328
|
+
function putBinding(idx, screen, b) {
|
|
2329
|
+
const list = (idx[screen] ?? []).filter((x) => !(x.component === b.component && x.slot === b.slot));
|
|
2330
|
+
return { ...idx, [screen]: [...list, b] };
|
|
2331
|
+
}
|
|
2332
|
+
function dropBinding(idx, screen, component, slot) {
|
|
2333
|
+
return { ...idx, [screen]: (idx[screen] ?? []).filter((x) => !(x.component === component && x.slot === slot)) };
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
// src/studio/useBindings.ts
|
|
2337
|
+
function useBindings(initial, save) {
|
|
2338
|
+
const [index, setIndex] = React13.useState(initial);
|
|
2407
2339
|
const [status, setStatus] = React13.useState("idle");
|
|
2408
2340
|
const [removed, setRemoved] = React13.useState(null);
|
|
2409
|
-
const
|
|
2341
|
+
const edits = React13.useRef(0);
|
|
2410
2342
|
const latest = React13.useRef(index);
|
|
2411
2343
|
latest.current = index;
|
|
2412
|
-
const screenIds = fw?.registry.screens.map((s) => s.id).join(`
|
|
2413
|
-
`) ?? "";
|
|
2414
|
-
React13.useEffect(() => {
|
|
2415
|
-
if (!screenIds)
|
|
2416
|
-
return;
|
|
2417
|
-
let live = true;
|
|
2418
|
-
loadAll(adapter, screenIds.split(`
|
|
2419
|
-
`)).then((i) => {
|
|
2420
|
-
if (live) {
|
|
2421
|
-
setIndex(i);
|
|
2422
|
-
setLoaded(true);
|
|
2423
|
-
}
|
|
2424
|
-
});
|
|
2425
|
-
return () => {
|
|
2426
|
-
live = false;
|
|
2427
|
-
};
|
|
2428
|
-
}, [adapter, screenIds]);
|
|
2429
2344
|
const flush = React13.useCallback(async () => {
|
|
2430
|
-
|
|
2345
|
+
const at = edits.current;
|
|
2346
|
+
if (!at)
|
|
2431
2347
|
return;
|
|
2432
2348
|
setStatus("saving");
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2349
|
+
try {
|
|
2350
|
+
await save(latest.current);
|
|
2351
|
+
if (edits.current === at)
|
|
2352
|
+
edits.current = 0;
|
|
2353
|
+
setStatus(edits.current ? "saving" : "saved");
|
|
2354
|
+
} catch {
|
|
2355
|
+
setStatus("failed");
|
|
2356
|
+
}
|
|
2357
|
+
}, [save]);
|
|
2436
2358
|
React13.useEffect(() => {
|
|
2437
|
-
if (!
|
|
2359
|
+
if (!edits.current)
|
|
2438
2360
|
return;
|
|
2439
2361
|
setStatus("saving");
|
|
2440
2362
|
const t = setTimeout(flush, 400);
|
|
@@ -2445,21 +2367,21 @@ function useBindings(fw, adapter) {
|
|
|
2445
2367
|
}, [flush]);
|
|
2446
2368
|
React13.useEffect(() => {
|
|
2447
2369
|
const warn = (e) => {
|
|
2448
|
-
if (
|
|
2370
|
+
if (edits.current)
|
|
2449
2371
|
e.preventDefault();
|
|
2450
2372
|
};
|
|
2451
2373
|
window.addEventListener("beforeunload", warn);
|
|
2452
2374
|
return () => window.removeEventListener("beforeunload", warn);
|
|
2453
2375
|
}, []);
|
|
2454
2376
|
const put = React13.useCallback((screen, b) => {
|
|
2455
|
-
|
|
2377
|
+
edits.current++;
|
|
2456
2378
|
setIndex((i) => putBinding(i, screen, b));
|
|
2457
2379
|
}, []);
|
|
2458
2380
|
const drop = React13.useCallback((screen, component, slot) => {
|
|
2459
2381
|
const binding = latest.current[screen]?.find((x) => x.component === component && x.slot === slot);
|
|
2460
2382
|
if (binding)
|
|
2461
2383
|
setRemoved({ screen, binding });
|
|
2462
|
-
|
|
2384
|
+
edits.current++;
|
|
2463
2385
|
setIndex((i) => dropBinding(i, screen, component, slot));
|
|
2464
2386
|
}, []);
|
|
2465
2387
|
const undo = React13.useCallback(() => {
|
|
@@ -2468,95 +2390,12 @@ function useBindings(fw, adapter) {
|
|
|
2468
2390
|
setRemoved(null);
|
|
2469
2391
|
}, [put, removed]);
|
|
2470
2392
|
const dismiss = React13.useCallback(() => setRemoved(null), []);
|
|
2471
|
-
|
|
2472
|
-
manifests.forEach((m) => markDirty(pending.current, m.screen));
|
|
2473
|
-
setIndex((i) => ({ ...i, ...Object.fromEntries(manifests.map((m) => [m.screen, m.bindings])) }));
|
|
2474
|
-
}, []);
|
|
2475
|
-
return { index, loaded, status, removed, put, drop, undo, dismiss, importManifests, retry: flush };
|
|
2476
|
-
}
|
|
2477
|
-
|
|
2478
|
-
// src/studio/useCatalog.ts
|
|
2479
|
-
import * as React14 from "react";
|
|
2480
|
-
|
|
2481
|
-
// src/framework/loadCatalog.ts
|
|
2482
|
-
import { parse } from "yaml";
|
|
2483
|
-
class CatalogLoadError extends Error {
|
|
2484
|
-
source;
|
|
2485
|
-
constructor(message, source) {
|
|
2486
|
-
super(message);
|
|
2487
|
-
this.source = source;
|
|
2488
|
-
this.name = "CatalogLoadError";
|
|
2489
|
-
}
|
|
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
|
-
}
|
|
2507
|
-
async function loadCatalog(url, init) {
|
|
2508
|
-
let res;
|
|
2509
|
-
try {
|
|
2510
|
-
res = await fetch(url, { ...init, headers: { Accept: "application/yaml, application/json, text/yaml, */*", ...init?.headers } });
|
|
2511
|
-
} catch {
|
|
2512
|
-
throw new CatalogLoadError("The docs could not be reached. Check the URL, your network, and that the docs server allows this site (CORS).", url);
|
|
2513
|
-
}
|
|
2514
|
-
if (!res.ok)
|
|
2515
|
-
throw new CatalogLoadError(`The docs server answered ${res.status} ${res.statusText}. Check the URL.`, url);
|
|
2516
|
-
return catalogFromText(await res.text(), url);
|
|
2517
|
-
}
|
|
2518
|
-
|
|
2519
|
-
// src/studio/useCatalog.ts
|
|
2520
|
-
function useCatalog(url, provided) {
|
|
2521
|
-
const [state, setState] = React14.useState(() => provided ? { status: "ready", catalog: provided, loadedAt: new Date, source: "provided" } : { status: "loading" });
|
|
2522
|
-
const [nonce, setNonce] = React14.useState(0);
|
|
2523
|
-
const [pasted, setPasted] = React14.useState(null);
|
|
2524
|
-
React14.useEffect(() => {
|
|
2525
|
-
if (pasted) {
|
|
2526
|
-
setState({ status: "ready", catalog: pasted, loadedAt: new Date, source: "pasted" });
|
|
2527
|
-
return;
|
|
2528
|
-
}
|
|
2529
|
-
if (provided) {
|
|
2530
|
-
setState({ status: "ready", catalog: provided, loadedAt: new Date, source: "provided" });
|
|
2531
|
-
return;
|
|
2532
|
-
}
|
|
2533
|
-
const ctrl = new AbortController;
|
|
2534
|
-
setState((s) => ({ status: "loading", catalog: s.catalog }));
|
|
2535
|
-
loadCatalog(url, { signal: ctrl.signal }).then((catalog) => setState({ status: "ready", catalog, loadedAt: new Date, source: "url" }), (e) => {
|
|
2536
|
-
if (!ctrl.signal.aborted)
|
|
2537
|
-
setState((s) => ({ status: "error", catalog: s.catalog, error: e instanceof Error ? e.message : String(e) }));
|
|
2538
|
-
});
|
|
2539
|
-
return () => ctrl.abort();
|
|
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];
|
|
2393
|
+
return { index, loaded: true, status, removed, put, drop, undo, dismiss, retry: flush };
|
|
2556
2394
|
}
|
|
2557
2395
|
|
|
2558
2396
|
// src/studio/BindingStudio.tsx
|
|
2559
2397
|
import { jsx as jsx18, jsxs as jsxs16, Fragment as Fragment10 } from "react/jsx-runtime";
|
|
2398
|
+
var same = (s) => s;
|
|
2560
2399
|
function SaveState({ status, onRetry }) {
|
|
2561
2400
|
return /* @__PURE__ */ jsx18("span", {
|
|
2562
2401
|
className: "abs-saved is-" + status,
|
|
@@ -2592,7 +2431,7 @@ function SaveState({ status, onRetry }) {
|
|
|
2592
2431
|
});
|
|
2593
2432
|
}
|
|
2594
2433
|
function UndoToast({ removed, label, onUndo, onDismiss }) {
|
|
2595
|
-
|
|
2434
|
+
React14.useEffect(() => {
|
|
2596
2435
|
const t = setTimeout(onDismiss, 6000);
|
|
2597
2436
|
return () => clearTimeout(t);
|
|
2598
2437
|
}, [removed, onDismiss]);
|
|
@@ -2632,15 +2471,45 @@ var EMPTY_HINT = /* @__PURE__ */ jsxs16("aside", {
|
|
|
2632
2471
|
})
|
|
2633
2472
|
]
|
|
2634
2473
|
});
|
|
2635
|
-
function BindingStudio({
|
|
2636
|
-
const [
|
|
2637
|
-
const [
|
|
2638
|
-
const
|
|
2639
|
-
|
|
2640
|
-
|
|
2474
|
+
function BindingStudio({ store, docsUrl = same, initialScreen, title = "Bind API", onClose }) {
|
|
2475
|
+
const [file, setFile] = React14.useState(null);
|
|
2476
|
+
const [fileError, setFileError] = React14.useState();
|
|
2477
|
+
const latest = React14.useRef({ file: null, index: {} });
|
|
2478
|
+
React14.useEffect(() => {
|
|
2479
|
+
let live = true;
|
|
2480
|
+
store.load().then((f) => {
|
|
2481
|
+
if (live) {
|
|
2482
|
+
latest.current.file = f;
|
|
2483
|
+
setFile(f);
|
|
2484
|
+
}
|
|
2485
|
+
}, (e) => live && setFileError(e instanceof Error ? e.message : String(e)));
|
|
2486
|
+
return () => {
|
|
2487
|
+
live = false;
|
|
2488
|
+
};
|
|
2489
|
+
}, [store]);
|
|
2490
|
+
const [catalogState, reloadCatalog, loadPasted] = useCatalog(file ? docsUrl(file.openapi) : "");
|
|
2491
|
+
const [settingsOpen, setSettingsOpen] = React14.useState(false);
|
|
2492
|
+
const closeSettings = React14.useCallback(() => setSettingsOpen(false), []);
|
|
2641
2493
|
const catalog = catalogState.catalog;
|
|
2642
|
-
const
|
|
2643
|
-
|
|
2494
|
+
const model = React14.useMemo(() => {
|
|
2495
|
+
if (!file || !catalog)
|
|
2496
|
+
return null;
|
|
2497
|
+
const { fw, index } = fromFile2(file, catalog);
|
|
2498
|
+
return { fw: { ...fw, registry: validateRegistry2(fw).registry }, index };
|
|
2499
|
+
}, [file?.kinds, file?.screens, file?.components, catalog]);
|
|
2500
|
+
const save = React14.useCallback(async (index) => {
|
|
2501
|
+
latest.current.index = index;
|
|
2502
|
+
if (latest.current.file)
|
|
2503
|
+
await store.save(toFile2(latest.current.file, index));
|
|
2504
|
+
}, [store]);
|
|
2505
|
+
const setOpenapi = (openapi) => {
|
|
2506
|
+
const f = latest.current.file;
|
|
2507
|
+
if (!f)
|
|
2508
|
+
return;
|
|
2509
|
+
latest.current.file = { ...f, openapi };
|
|
2510
|
+
setFile(latest.current.file);
|
|
2511
|
+
store.save(toFile2(latest.current.file, Object.keys(latest.current.index).length ? latest.current.index : model?.index ?? {}));
|
|
2512
|
+
};
|
|
2644
2513
|
const settingsButton = /* @__PURE__ */ jsxs16("div", {
|
|
2645
2514
|
className: "abs-settings-wrap",
|
|
2646
2515
|
children: [
|
|
@@ -2669,19 +2538,18 @@ function BindingStudio({ framework, docsUrl = DEFAULT_DOCS_URL, persistence, ini
|
|
|
2669
2538
|
name: "close"
|
|
2670
2539
|
})
|
|
2671
2540
|
}) : null,
|
|
2672
|
-
settingsOpen ? /* @__PURE__ */ jsx18(SettingsPanel, {
|
|
2673
|
-
docsUrl:
|
|
2674
|
-
onDocsUrl:
|
|
2541
|
+
settingsOpen && file ? /* @__PURE__ */ jsx18(SettingsPanel, {
|
|
2542
|
+
docsUrl: file.openapi,
|
|
2543
|
+
onDocsUrl: setOpenapi,
|
|
2675
2544
|
catalog: catalogState,
|
|
2676
2545
|
onReload: reloadCatalog,
|
|
2677
2546
|
onPaste: loadPasted,
|
|
2678
|
-
manifests: () => fw ? Object.entries(binds.index).map(([s, b]) => toManifest2(fw, s, b)) : [],
|
|
2679
|
-
onImport: binds.importManifests,
|
|
2680
2547
|
onClose: closeSettings
|
|
2681
2548
|
}) : null
|
|
2682
2549
|
]
|
|
2683
2550
|
});
|
|
2684
|
-
if (!
|
|
2551
|
+
if (!model) {
|
|
2552
|
+
const error = fileError ?? (catalogState.status === "error" ? catalogState.error : undefined);
|
|
2685
2553
|
return /* @__PURE__ */ jsxs16("div", {
|
|
2686
2554
|
className: "abs-root",
|
|
2687
2555
|
children: [
|
|
@@ -2698,7 +2566,7 @@ function BindingStudio({ framework, docsUrl = DEFAULT_DOCS_URL, persistence, ini
|
|
|
2698
2566
|
}),
|
|
2699
2567
|
/* @__PURE__ */ jsx18("div", {
|
|
2700
2568
|
className: "abs-state-page",
|
|
2701
|
-
children:
|
|
2569
|
+
children: error ? /* @__PURE__ */ jsxs16(Fragment10, {
|
|
2702
2570
|
children: [
|
|
2703
2571
|
/* @__PURE__ */ jsx18("span", {
|
|
2704
2572
|
className: "abs-empty-ico is-bad",
|
|
@@ -2708,12 +2576,12 @@ function BindingStudio({ framework, docsUrl = DEFAULT_DOCS_URL, persistence, ini
|
|
|
2708
2576
|
})
|
|
2709
2577
|
}),
|
|
2710
2578
|
/* @__PURE__ */ jsx18("b", {
|
|
2711
|
-
children: "The API docs did not load"
|
|
2579
|
+
children: fileError ? "The bindings file did not load" : "The API docs did not load"
|
|
2712
2580
|
}),
|
|
2713
2581
|
/* @__PURE__ */ jsx18("span", {
|
|
2714
|
-
children:
|
|
2582
|
+
children: error
|
|
2715
2583
|
}),
|
|
2716
|
-
/* @__PURE__ */ jsxs16("div", {
|
|
2584
|
+
fileError ? null : /* @__PURE__ */ jsxs16("div", {
|
|
2717
2585
|
className: "abs-set-row",
|
|
2718
2586
|
children: [
|
|
2719
2587
|
/* @__PURE__ */ jsxs16("button", {
|
|
@@ -2745,7 +2613,7 @@ function BindingStudio({ framework, docsUrl = DEFAULT_DOCS_URL, persistence, ini
|
|
|
2745
2613
|
}),
|
|
2746
2614
|
/* @__PURE__ */ jsx18("span", {
|
|
2747
2615
|
"aria-live": "polite",
|
|
2748
|
-
children: "Loading API docs…"
|
|
2616
|
+
children: file ? "Loading API docs…" : "Loading bindings…"
|
|
2749
2617
|
})
|
|
2750
2618
|
]
|
|
2751
2619
|
})
|
|
@@ -2754,26 +2622,27 @@ function BindingStudio({ framework, docsUrl = DEFAULT_DOCS_URL, persistence, ini
|
|
|
2754
2622
|
});
|
|
2755
2623
|
}
|
|
2756
2624
|
return /* @__PURE__ */ jsx18(Workbench, {
|
|
2757
|
-
fw,
|
|
2758
|
-
|
|
2625
|
+
fw: model.fw,
|
|
2626
|
+
initial: model.index,
|
|
2627
|
+
save,
|
|
2759
2628
|
title,
|
|
2760
2629
|
initialScreen,
|
|
2761
2630
|
settingsButton
|
|
2762
2631
|
});
|
|
2763
2632
|
}
|
|
2764
|
-
function Workbench({ fw,
|
|
2765
|
-
const { index, loaded, status, removed, put, drop, undo, dismiss, retry } =
|
|
2766
|
-
const [place, go] =
|
|
2767
|
-
const engine =
|
|
2633
|
+
function Workbench({ fw, initial, save, title, initialScreen, settingsButton }) {
|
|
2634
|
+
const { index, loaded, status, removed, put, drop, undo, dismiss, retry } = useBindings(initial, save);
|
|
2635
|
+
const [place, go] = React14.useState({ screen: initialScreen });
|
|
2636
|
+
const engine = React14.useMemo(() => createEngine2(fw, index), [fw, index]);
|
|
2768
2637
|
const screen = place.screen && engine.SCR.get(place.screen) || fw.registry.screens[0];
|
|
2769
2638
|
const comp = screen && place.component ? engine.compOf(screen.id, place.component) : undefined;
|
|
2770
2639
|
const screenId = screen?.id;
|
|
2771
|
-
const select =
|
|
2772
|
-
const putHere =
|
|
2640
|
+
const select = React14.useCallback((component) => go({ screen: screenId, component }), [go, screenId]);
|
|
2641
|
+
const putHere = React14.useCallback((b) => {
|
|
2773
2642
|
if (screenId)
|
|
2774
2643
|
put(screenId, b);
|
|
2775
2644
|
}, [put, screenId]);
|
|
2776
|
-
const dropHere =
|
|
2645
|
+
const dropHere = React14.useCallback((c, s) => {
|
|
2777
2646
|
if (screenId)
|
|
2778
2647
|
drop(screenId, c, s);
|
|
2779
2648
|
}, [drop, screenId]);
|
|
@@ -2861,87 +2730,9 @@ function Workbench({ fw, binds, title, initialScreen, settingsButton }) {
|
|
|
2861
2730
|
]
|
|
2862
2731
|
});
|
|
2863
2732
|
}
|
|
2864
|
-
// src/studio/BindingWidget.tsx
|
|
2865
|
-
import * as React16 from "react";
|
|
2866
|
-
import { jsx as jsx19, jsxs as jsxs17, Fragment as Fragment11 } from "react/jsx-runtime";
|
|
2867
|
-
var fallbackStore = localStorageAdapter2();
|
|
2868
|
-
function BindingWidget({ framework, docsUrl, title }) {
|
|
2869
|
-
const runtime = useBindingRuntime();
|
|
2870
|
-
const dialog = React16.useRef(null);
|
|
2871
|
-
const fab = React16.useRef(null);
|
|
2872
|
-
const [state, setState] = React16.useState("closed");
|
|
2873
|
-
const [screen, setScreen] = React16.useState();
|
|
2874
|
-
const open = () => {
|
|
2875
|
-
setScreen(runtime.activeScreen());
|
|
2876
|
-
setState("open");
|
|
2877
|
-
};
|
|
2878
|
-
const requestClose = React16.useCallback(() => setState((s) => s === "open" ? "closing" : s), []);
|
|
2879
|
-
React16.useLayoutEffect(() => {
|
|
2880
|
-
if (state === "open" && !dialog.current?.open)
|
|
2881
|
-
dialog.current?.showModal();
|
|
2882
|
-
}, [state]);
|
|
2883
|
-
const finishClose = (e) => {
|
|
2884
|
-
if (e.target !== dialog.current || state !== "closing")
|
|
2885
|
-
return;
|
|
2886
|
-
dialog.current?.close();
|
|
2887
|
-
setState("closed");
|
|
2888
|
-
runtime.reloadSaved();
|
|
2889
|
-
fab.current?.focus();
|
|
2890
|
-
};
|
|
2891
|
-
return /* @__PURE__ */ jsxs17(Fragment11, {
|
|
2892
|
-
children: [
|
|
2893
|
-
/* @__PURE__ */ jsxs17("button", {
|
|
2894
|
-
ref: fab,
|
|
2895
|
-
type: "button",
|
|
2896
|
-
className: "abs-fab",
|
|
2897
|
-
onClick: open,
|
|
2898
|
-
"aria-haspopup": "dialog",
|
|
2899
|
-
"aria-expanded": state !== "closed",
|
|
2900
|
-
children: [
|
|
2901
|
-
/* @__PURE__ */ jsx19("span", {
|
|
2902
|
-
className: "abs-fab-ico",
|
|
2903
|
-
children: /* @__PURE__ */ jsx19(Icon, {
|
|
2904
|
-
name: "link",
|
|
2905
|
-
size: 18
|
|
2906
|
-
})
|
|
2907
|
-
}),
|
|
2908
|
-
/* @__PURE__ */ jsx19("span", {
|
|
2909
|
-
className: "abs-fab-label",
|
|
2910
|
-
children: /* @__PURE__ */ jsx19("span", {
|
|
2911
|
-
children: title ?? "Bind API"
|
|
2912
|
-
})
|
|
2913
|
-
})
|
|
2914
|
-
]
|
|
2915
|
-
}),
|
|
2916
|
-
state !== "closed" ? /* @__PURE__ */ jsx19("dialog", {
|
|
2917
|
-
ref: dialog,
|
|
2918
|
-
className: "abs-widget" + (state === "closing" ? " is-closing" : ""),
|
|
2919
|
-
"aria-label": title ?? "Bind API",
|
|
2920
|
-
onCancel: (e) => {
|
|
2921
|
-
e.preventDefault();
|
|
2922
|
-
requestClose();
|
|
2923
|
-
},
|
|
2924
|
-
onClick: (e) => {
|
|
2925
|
-
if (e.target === e.currentTarget)
|
|
2926
|
-
requestClose();
|
|
2927
|
-
},
|
|
2928
|
-
onAnimationEnd: finishClose,
|
|
2929
|
-
children: /* @__PURE__ */ jsx19(BindingStudio, {
|
|
2930
|
-
framework,
|
|
2931
|
-
docsUrl,
|
|
2932
|
-
title,
|
|
2933
|
-
initialScreen: screen,
|
|
2934
|
-
persistence: runtime.persistence ?? fallbackStore,
|
|
2935
|
-
onClose: requestClose
|
|
2936
|
-
})
|
|
2937
|
-
}) : null
|
|
2938
|
-
]
|
|
2939
|
-
});
|
|
2940
|
-
}
|
|
2941
2733
|
export {
|
|
2942
|
-
BindingStudio
|
|
2943
|
-
BindingWidget
|
|
2734
|
+
BindingStudio
|
|
2944
2735
|
};
|
|
2945
2736
|
|
|
2946
|
-
//# debugId=
|
|
2737
|
+
//# debugId=5CC922DF10302EB664756E2164756E21
|
|
2947
2738
|
//# sourceMappingURL=index.js.map
|